Skip to content

Commit 734b26d

Browse files
prattmicgopherbot
authored andcommitted
runtime: exclude extra M's from debug.SetMaxThreads
The purpose of the debug.SetMaxThreads limit is to avoid accidental fork bomb from something like millions of goroutines blocking on system calls, causing the runtime to create millions of threads. By definition we don't create threads created in C, so this isn't a problem for those threads, and we can exclude them from the limit. If C wants to create tens of thousands of threads, who are we to say no? Fixes #60004. Change-Id: I62b875890718b406abca42a9a4078391e25aa21b Reviewed-on: https://go-review.googlesource.com/c/go/+/492743 Auto-Submit: Michael Pratt <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Cherry Mui <[email protected]> Run-TryBot: Michael Pratt <[email protected]>
1 parent 5751939 commit 734b26d

File tree

1 file changed

+25
-4
lines changed

1 file changed

+25
-4
lines changed

src/runtime/proc.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,16 @@ func dumpgstatus(gp *g) {
779779
func checkmcount() {
780780
assertLockHeld(&sched.lock)
781781

782-
if mcount() > sched.maxmcount {
782+
// Exclude extra M's, which are used for cgocallback from threads
783+
// created in C.
784+
//
785+
// The purpose of the SetMaxThreads limit is to avoid accidental fork
786+
// bomb from something like millions of goroutines blocking on system
787+
// calls, causing the runtime to create millions of threads. By
788+
// definition, this isn't a problem for threads created in C, so we
789+
// exclude them from the limit. See https://go.dev/issue/60004.
790+
count := mcount() - int32(extraMInUse.Load()) - int32(extraMLength.Load())
791+
if count > sched.maxmcount {
783792
print("runtime: program exceeds ", sched.maxmcount, "-thread limit\n")
784793
throw("thread exhaustion")
785794
}
@@ -2015,7 +2024,7 @@ func oneNewExtraM() {
20152024
sched.ngsys.Add(1)
20162025

20172026
// Add m to the extra list.
2018-
putExtraM(mp)
2027+
addExtraM(mp)
20192028
}
20202029

20212030
// dropm is called when a cgo callback has called needm but is now
@@ -2084,6 +2093,9 @@ var (
20842093
extraMLength atomic.Uint32
20852094
// Number of waiters in lockextra.
20862095
extraMWaiters atomic.Uint32
2096+
2097+
// Number of extra M's in use by threads.
2098+
extraMInUse atomic.Uint32
20872099
)
20882100

20892101
// lockextra locks the extra list and returns the list head.
@@ -2115,6 +2127,7 @@ func lockextra(nilokay bool) *m {
21152127
continue
21162128
}
21172129
if extraM.CompareAndSwap(old, locked) {
2130+
extraMInUse.Add(1)
21182131
return (*m)(unsafe.Pointer(old))
21192132
}
21202133
osyield_no_g()
@@ -2128,7 +2141,6 @@ func unlockextra(mp *m, delta int32) {
21282141
extraM.Store(uintptr(unsafe.Pointer(mp)))
21292142
}
21302143

2131-
21322144
// Return an M from the extra M list. Returns last == true if the list becomes
21332145
// empty because of this call.
21342146
//
@@ -2143,10 +2155,19 @@ func getExtraM(nilokay bool) (mp *m, last bool) {
21432155
return mp, mp.schedlink.ptr() == nil
21442156
}
21452157

2146-
// Put an extra M on the list.
2158+
// Returns an extra M back to the list. mp must be from getExtraM. Newly
2159+
// allocated M's should use addExtraM.
21472160
//
21482161
//go:nosplit
21492162
func putExtraM(mp *m) {
2163+
extraMInUse.Add(-1)
2164+
addExtraM(mp)
2165+
}
2166+
2167+
// Adds a newly allocated M to the extra M list.
2168+
//
2169+
//go:nosplit
2170+
func addExtraM(mp *m) {
21502171
mnext := lockextra(true)
21512172
mp.schedlink.set(mnext)
21522173
unlockextra(mp, 1)

0 commit comments

Comments
 (0)