aboutsummaryrefslogtreecommitdiffstats
path: root/library/go/cgosem/sem.go
blob: 357e0529db110b1c10640b3f15a2852f42235023 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Package cgosem implements fast and imprecise semaphore used to globally limit concurrency of _fast_ cgo calls.
//
// In the future, when go runtime scheduler gets smarter and stop suffering from uncontrolled growth the number of
// system threads, this package should be removed.
//
// See "Cgoroutines != Goroutines" section of https://www.cockroachlabs.com/blog/the-cost-and-complexity-of-cgo/
// for explanation of the thread leak problem.
//
// To use this semaphore, put the following line at the beginning of the function doing Cgo calls.
//
//	defer cgosem.S.Acquire().Release()
//
// This will globally limit number of concurrent Cgo calls to GOMAXPROCS, limiting number of additional threads created by the
// go runtime to the same number.
//
// Overhead of this semaphore is about 1us, which should be negligible compared to the work you are trying to do in the C function.
//
// To see code in action, run:
//
//	ya make -r library/go/cgosem/gotest
//	env GODEBUG=schedtrace=1000,scheddetail=1 library/go/cgosem/gotest/gotest --test.run TestLeak
//	env GODEBUG=schedtrace=1000,scheddetail=1 library/go/cgosem/gotest/gotest --test.run TestLeakFix
//
// And look for the number of created M's.
package cgosem

import "runtime"

type Sem chan struct{}

// new creates new semaphore with max concurrency of n.
func newSem(n int) (s Sem) {
	s = make(chan struct{}, n)
	for i := 0; i < n; i++ {
		s <- struct{}{}
	}
	return
}

func (s Sem) Acquire() Sem {
	if s == nil {
		return nil
	}

	<-s
	return s
}

func (s Sem) Release() {
	if s == nil {
		return
	}

	s <- struct{}{}
}

// S is global semaphore with good enough settings for most cgo libraries.
var S Sem

// Disable global cgo semaphore. Must be called from init() function.
func Disable() {
	S = nil
}

func init() {
	S = newSem(runtime.GOMAXPROCS(0))
}