Commit 60586034 authored by Austin Clements's avatar Austin Clements

runtime: only shrink stacks at synchronous safe points

We're about to introduce asynchronous safe points, where we won't have
precise pointer maps for all stack frames. That's okay for scanning
the stack (conservatively), but not for shrinking the stack.

Hence, this CL prepares for this by only shrinking the stack as part
of the stack scan if the goroutine is stopped at a synchronous safe
point. Otherwise, it queues up the stack shrink for the next
synchronous safe point.

We already have one condition under which we can't shrink the stack
for very similar reasons: syscalls. Currently, we just give up on
shrinking the stack if it's in a syscall. But with this mechanism, we
defer that stack shrink until the next synchronous safe point.

For #10958, #24543.

Change-Id: Ifa1dec6f33fdf30f9067be2ce3f7ab8a7f62ce38
Reviewed-on: https://go-review.googlesource.com/c/go/+/201438
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarCherry Zhang <cherryyz@google.com>
parent 36a432f2
...@@ -667,6 +667,10 @@ func gcFlushBgCredit(scanWork int64) { ...@@ -667,6 +667,10 @@ func gcFlushBgCredit(scanWork int64) {
// scanstack scans gp's stack, greying all pointers found on the stack. // scanstack scans gp's stack, greying all pointers found on the stack.
// //
// scanstack will also shrink the stack if it is safe to do so. If it
// is not, it schedules a stack shrink for the next synchronous safe
// point.
//
// scanstack is marked go:systemstack because it must not be preempted // scanstack is marked go:systemstack because it must not be preempted
// while using a workbuf. // while using a workbuf.
// //
...@@ -695,8 +699,13 @@ func scanstack(gp *g, gcw *gcWork) { ...@@ -695,8 +699,13 @@ func scanstack(gp *g, gcw *gcWork) {
throw("can't scan our own stack") throw("can't scan our own stack")
} }
// Shrink the stack if not much of it is being used. if isShrinkStackSafe(gp) {
shrinkstack(gp) // Shrink the stack if not much of it is being used.
shrinkstack(gp)
} else {
// Otherwise, shrink the stack at the next sync safe point.
gp.preemptShrink = true
}
var state stackScanState var state stackScanState
state.stack = gp.stack state.stack = gp.stack
......
...@@ -418,11 +418,14 @@ type g struct { ...@@ -418,11 +418,14 @@ type g struct {
schedlink guintptr schedlink guintptr
waitsince int64 // approx time when the g become blocked waitsince int64 // approx time when the g become blocked
waitreason waitReason // if status==Gwaiting waitreason waitReason // if status==Gwaiting
preempt bool // preemption signal, duplicates stackguard0 = stackpreempt
preemptStop bool // transition to _Gpreempted on preemption; otherwise, just deschedule preempt bool // preemption signal, duplicates stackguard0 = stackpreempt
paniconfault bool // panic (instead of crash) on unexpected fault address preemptStop bool // transition to _Gpreempted on preemption; otherwise, just deschedule
gcscandone bool // g has scanned stack; protected by _Gscan bit in status preemptShrink bool // shrink stack at synchronous safe point
throwsplit bool // must not split stack
paniconfault bool // panic (instead of crash) on unexpected fault address
gcscandone bool // g has scanned stack; protected by _Gscan bit in status
throwsplit bool // must not split stack
// activeStackChans indicates that there are unlocked channels // activeStackChans indicates that there are unlocked channels
// pointing into this goroutine's stack. If true, stack // pointing into this goroutine's stack. If true, stack
// copying needs to acquire channel locks to protect these // copying needs to acquire channel locks to protect these
......
...@@ -1010,6 +1010,13 @@ func newstack() { ...@@ -1010,6 +1010,13 @@ func newstack() {
throw("runtime: g is running but p is not") throw("runtime: g is running but p is not")
} }
if gp.preemptShrink {
// We're at a synchronous safe point now, so
// do the pending stack shrink.
gp.preemptShrink = false
shrinkstack(gp)
}
if gp.preemptStop { if gp.preemptStop {
preemptPark(gp) // never returns preemptPark(gp) // never returns
} }
...@@ -1057,16 +1064,36 @@ func gostartcallfn(gobuf *gobuf, fv *funcval) { ...@@ -1057,16 +1064,36 @@ func gostartcallfn(gobuf *gobuf, fv *funcval) {
gostartcall(gobuf, fn, unsafe.Pointer(fv)) gostartcall(gobuf, fn, unsafe.Pointer(fv))
} }
// isShrinkStackSafe returns whether it's safe to attempt to shrink
// gp's stack. Shrinking the stack is only safe when we have precise
// pointer maps for all frames on the stack.
func isShrinkStackSafe(gp *g) bool {
// We can't copy the stack if we're in a syscall.
// The syscall might have pointers into the stack and
// often we don't have precise pointer maps for the innermost
// frames.
return gp.syscallsp == 0
}
// Maybe shrink the stack being used by gp. // Maybe shrink the stack being used by gp.
// Called at garbage collection time. //
// gp must be stopped, but the world need not be. // gp must be stopped and we must own its stack. It may be in
// _Grunning, but only if this is our own user G.
func shrinkstack(gp *g) { func shrinkstack(gp *g) {
gstatus := readgstatus(gp)
if gp.stack.lo == 0 { if gp.stack.lo == 0 {
throw("missing stack in shrinkstack") throw("missing stack in shrinkstack")
} }
if gstatus&_Gscan == 0 { if s := readgstatus(gp); s&_Gscan == 0 {
throw("bad status in shrinkstack") // We don't own the stack via _Gscan. We could still
// own it if this is our own user G and we're on the
// system stack.
if !(gp == getg().m.curg && getg() != getg().m.curg && s == _Grunning) {
// We don't own the stack.
throw("bad status in shrinkstack")
}
}
if !isShrinkStackSafe(gp) {
throw("shrinkstack at bad time")
} }
// Check for self-shrinks while in a libcall. These may have // Check for self-shrinks while in a libcall. These may have
// pointers into the stack disguised as uintptrs, but these // pointers into the stack disguised as uintptrs, but these
...@@ -1102,12 +1129,6 @@ func shrinkstack(gp *g) { ...@@ -1102,12 +1129,6 @@ func shrinkstack(gp *g) {
return return
} }
// We can't copy the stack if we're in a syscall.
// The syscall might have pointers into the stack.
if gp.syscallsp != 0 {
return
}
if stackDebug > 0 { if stackDebug > 0 {
print("shrinking stack ", oldsize, "->", newsize, "\n") print("shrinking stack ", oldsize, "->", newsize, "\n")
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment