From e481a08e0ecb823f368318b142d3dec7340fe51e Mon Sep 17 00:00:00 2001 From: Michael Pratt Date: Wed, 28 May 2025 16:01:33 -0400 Subject: runtime: guarantee no GOMAXPROCS update syscalls after GOMAXPROCS call We already guarantee that no automatic updates to GOMAXPROCS occur after a GOMAXPROCS call returns. This is easily achieved by having the update goroutine double-check that updates are still allowed during STW before committing the new value. However, it is possible for sysmon to concurrently run defaultGOMAXPROCS to compute a new GOMAXPROCS value after GOMAXPROCS returns. This new value will be discarded later, but we'll still perform the system calls necessary to compute the new value. Normally this distinction doesn't matter, but if you want to sandbox a Go program, then you may want to disable GOMAXPROCS updates to reduce the system call footprint. A call to GOMAXPROCS will disable updates, but without a guarantee on when sysmon will observe the change it is somewhat fragile. Add explicit synchronization between GOMAXPROCS and sysmon to guarantee that sysmon won't run defaultGOMAXPROCS after GOMAXPROCS returns. The synchronization is a bit complex because we can't hold a mutex across STW, nor take a semaphore from sysmon, but the result isn't too bad. One oddity is that sched.customGOMAXPROCS and gomaxprocs are no longer updated in lockstep (even though both are protected by sched.lock), but I don't believe anything should depend on that. For #73193. Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-staticlockranking Change-Id: I6a6a636cff243a9b69ac1b5d2f98925648e60236 Reviewed-on: https://go-review.googlesource.com/c/go/+/677037 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek --- src/runtime/debug.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'src/runtime/debug.go') diff --git a/src/runtime/debug.go b/src/runtime/debug.go index 94d1dab34d..bdaaa7196d 100644 --- a/src/runtime/debug.go +++ b/src/runtime/debug.go @@ -39,10 +39,18 @@ func GOMAXPROCS(n int) int { lock(&sched.lock) ret := int(gomaxprocs) - unlock(&sched.lock) if n <= 0 || n == ret { + unlock(&sched.lock) return ret } + // Set early so we can wait for sysmon befor STW. See comment on + // computeMaxProcsLock. + sched.customGOMAXPROCS = true + unlock(&sched.lock) + + // Wait for sysmon to complete running defaultGOMAXPROCS. + lock(&computeMaxProcsLock) + unlock(&computeMaxProcsLock) stw := stopTheWorldGC(stwGOMAXPROCS) @@ -51,7 +59,6 @@ func GOMAXPROCS(n int) int { // TODO(prattmic): this could use a nicer API. Perhaps add it to the // stw parameter? newprocs = int32(n) - newprocsCustom = true startTheWorldGC(stw) return ret @@ -91,7 +98,9 @@ func SetDefaultGOMAXPROCS() { // TODO(prattmic): this could use a nicer API. Perhaps add it to the // stw parameter? newprocs = procs - newprocsCustom = false + lock(&sched.lock) + sched.customGOMAXPROCS = false + unlock(&sched.lock) startTheWorldGC(stw) } -- cgit v1.3