runtime: disable stack shrinking for all waiting-for-suspendG cases

Currently isShrinkStackSafe returns false if a goroutine is put into
_Gwaiting while it actually goes and executes on the system stack.
For a long time, we needed to be robust to the goroutine's stack
shrinking while we're executing on the system stack.

Unfortunately, this has become harder and harder to do over time. First,
the execution tracer might be invoked in these contexts and it may wish
to take a stack trace. We cannot take the stack trace if the garbage
collector might concurrently shrink the stack of the user goroutine we
want to trace. So, isShrinkStackSafe grew the condition that we wouldn't
try to shrink the stack in these cases if execution tracing was enabled.

Today, runtime.mutex may wish to take a stack trace for the mutex
profile, and it can happen in a very similar context. Taking the stack
trace is no longer safe.

This change takes the stance that we stop trying to make this work at
all, and instead guarantee that the stack won't move while we're in
these sensitive contexts.

Change-Id: Ibfad2d7a335ee97cecaa48001df0db9812deeab1
Reviewed-on: https://go-review.googlesource.com/c/go/+/692716
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
Michael Anthony Knyszek 2025-08-02 03:26:43 +00:00 committed by Gopher Robot
parent a651e2ea47
commit bd07fafb0a
3 changed files with 27 additions and 42 deletions

View file

@ -1214,15 +1214,18 @@ func isShrinkStackSafe(gp *g) bool {
if gp.parkingOnChan.Load() {
return false
}
// We also can't copy the stack while tracing is enabled, and
// gp is in _Gwaiting solely to make itself available to suspendG.
// We also can't copy the stack while a gp is in _Gwaiting solely
// to make itself available to suspendG.
//
// In these cases, the G is actually executing on the system
// stack, and the execution tracer may want to take a stack trace
// of the G's stack. Note: it's safe to access gp.waitreason here.
// We're only checking if this is true if we took ownership of the
// stack, and the execution tracer, mutex profiler, etc. may want
// to take a stack trace of the G's stack.
//
// Note: it's safe to access gp.waitreason here.
// We're only calling isShrinkStackSafe if we took ownership of the
// G with the _Gscan bit. This prevents the goroutine from transitioning,
// which prevents gp.waitreason from changing.
if traceEnabled() && readgstatus(gp)&^_Gscan == _Gwaiting && gp.waitreason.isWaitingForSuspendG() {
if readgstatus(gp)&^_Gscan == _Gwaiting && gp.waitreason.isWaitingForSuspendG() {
return false
}
return true
@ -1258,12 +1261,6 @@ func shrinkstack(gp *g) {
if debug.gcshrinkstackoff > 0 {
return
}
f := findfunc(gp.startpc)
if f.valid() && f.funcID == abi.FuncID_gcBgMarkWorker {
// We're not allowed to shrink the gcBgMarkWorker
// stack (see gcBgMarkWorker for explanation).
return
}
oldsize := gp.stack.hi - gp.stack.lo
newsize := oldsize / 2