aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAustin Clements <austin@google.com>2015-08-06 15:36:50 -0400
committerAustin Clements <austin@google.com>2015-08-06 20:21:05 +0000
commitad731887a76e4bd5060a212002091ece3740068d (patch)
tree7ca9aee3c58c23b6fcfd9f8b47c40e6669fafb2a
parent26baed6af78fb5ec80b945ed11875e245403c7fd (diff)
downloadgo-ad731887a76e4bd5060a212002091ece3740068d.tar.gz
go-ad731887a76e4bd5060a212002091ece3740068d.zip
runtime: call goexit1 instead of goexit
Currently, runtime.Goexit() calls goexit()—the goroutine exit stub—to terminate the goroutine. This *mostly* works, but can cause a "leftover stack barriers" panic if the following happens: 1. Goroutine A has a reasonably large stack. 2. The garbage collector scan phase runs and installs stack barriers in A's stack. The top-most stack barrier happens to fall at address X. 3. Goroutine A unwinds the stack far enough to be a candidate for stack shrinking, but not past X. 4. Goroutine A calls runtime.Goexit(), which calls goexit(), which calls goexit1(). 5. The garbage collector enters mark termination. 6. Goroutine A is preempted right at the prologue of goexit1() and performs a stack shrink, which calls gentraceback. gentraceback stops as soon as it sees goexit on the stack, which is only two frames up at this point, even though there may really be many frames above it. More to the point, the stack barrier at X is above the goexit frame, so gentraceback never sees that stack barrier. At the end of gentraceback, it checks that it saw all of the stack barriers and panics because it didn't see the one at X. The fix is simple: call goexit1, which actually implements the process of exiting a goroutine, rather than goexit, the exit stub. To make sure this doesn't happen again in the future, we also add an argument to the stub prototype of goexit so you really, really have to want to call it in order to call it. We were able to reliably reproduce the above sequence with a fair amount of awful code inserted at the right places in the runtime, but chose to change the goexit prototype to ensure this wouldn't happen again rather than pollute the runtime with ugly testing code. Change-Id: Ifb6fb53087e09a252baddadc36eebf954468f2a8 Reviewed-on: https://go-review.googlesource.com/13323 Reviewed-by: Russ Cox <rsc@golang.org>
-rw-r--r--src/runtime/panic.go2
-rw-r--r--src/runtime/stubs.go15
2 files changed, 15 insertions, 2 deletions
diff --git a/src/runtime/panic.go b/src/runtime/panic.go
index c8158b9dec..a1662812de 100644
--- a/src/runtime/panic.go
+++ b/src/runtime/panic.go
@@ -327,7 +327,7 @@ func Goexit() {
freedefer(d)
// Note: we ignore recovers here because Goexit isn't a panic
}
- goexit()
+ goexit1()
}
// Print all currently active panics. Used when crashing.
diff --git a/src/runtime/stubs.go b/src/runtime/stubs.go
index e6b015684b..d725bb11f5 100644
--- a/src/runtime/stubs.go
+++ b/src/runtime/stubs.go
@@ -128,7 +128,20 @@ func breakpoint()
func reflectcall(argtype *_type, fn, arg unsafe.Pointer, argsize uint32, retoffset uint32)
func procyield(cycles uint32)
-func goexit()
+
+type neverCallThisFunction struct{}
+
+// goexit is the return stub at the top of every goroutine call stack.
+// Each goroutine stack is constructed as if goexit called the
+// goroutine's entry point function, so that when the entry point
+// function returns, it will return to goexit, which will call goexit1
+// to perform the actual exit.
+//
+// This function must never be called directly. Call goexit1 instead.
+// gentraceback assumes that goexit terminates the stack. A direct
+// call on the stack will cause gentraceback to stop walking the stack
+// prematurely and if there are leftover stack barriers it may panic.
+func goexit(neverCallThisFunction)
// Not all cgocallback_gofunc frames are actually cgocallback_gofunc,
// so not all have these arguments. Mark them uintptr so that the GC