aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAustin Clements <austin@google.com>2018-01-18 14:58:05 -0500
committerAustin Clements <austin@google.com>2018-01-23 20:08:46 +0000
commit2edc4d46340ca64dfc4dbcb8433868b6f29a7a07 (patch)
tree96aab5ed6739dacbd3b3749db53a2f5cb8e86acb
parent9483a0bc23904af80e47aaa8cf1239b3012246d2 (diff)
downloadgo-2edc4d46340ca64dfc4dbcb8433868b6f29a7a07.tar.gz
go-2edc4d46340ca64dfc4dbcb8433868b6f29a7a07.zip
runtime: never allocate during an unrecoverable panic
Currently, startpanic_m (which prepares for an unrecoverable panic) goes out of its way to make it possible to allocate during panic handling by allocating an mcache if there isn't one. However, this is both potentially dangerous and unnecessary. Allocating an mcache is a generally complex thing to do in an already precarious situation. Specifically, it requires obtaining the heap lock, and there's evidence that this may be able to deadlock (#23360). However, it's also unnecessary because we never allocate from the unrecoverable panic path. This didn't use to be the case. The call to allocmcache was introduced long ago, in CL 7388043, where it was in preparation for separating Ms and Ps and potentially running an M without an mcache. At the time, after calling startpanic, the runtime could call String and Error methods on panicked values, which could do anything including allocating. That was generally unsafe even at the time, and CL 19792 fixed this be pre-printing panic messages before calling startpanic. As a result, we now no longer allocate after calling startpanic. This CL not only removes the allocmcache call, but goes a step further to explicitly disallow any allocation during unrecoverable panic handling, even in situations where it might be safe. This way, if panic handling ever does an allocation that would be unsafe in unusual circumstances, we'll know even if it happens during normal circumstances. This would help with debugging #23360, since the deadlock in allocmcache is currently masking the real failure. Beyond all.bash, I manually tested this change by adding panics at various points in early runtime init, signal handling, and the scheduler to check unusual panic situations. Change-Id: I85df21e2b4b20c6faf1f13fae266c9339eebc061 Reviewed-on: https://go-review.googlesource.com/88835 Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
-rw-r--r--src/runtime/error.go3
-rw-r--r--src/runtime/panic.go13
2 files changed, 10 insertions, 6 deletions
diff --git a/src/runtime/error.go b/src/runtime/error.go
index 16f3e53a47..6048272e75 100644
--- a/src/runtime/error.go
+++ b/src/runtime/error.go
@@ -72,8 +72,7 @@ func typestring(x interface{}) string {
return e._type.string()
}
-// For calling from C.
-// Prints an argument passed to panic.
+// printany prints an argument passed to panic.
func printany(i interface{}) {
switch v := i.(type) {
case nil:
diff --git a/src/runtime/panic.go b/src/runtime/panic.go
index 106ca5bffc..c51948bd18 100644
--- a/src/runtime/panic.go
+++ b/src/runtime/panic.go
@@ -408,12 +408,15 @@ func preprintpanics(p *_panic) {
}
// Print all currently active panics. Used when crashing.
+// Should only be called after preprintpanics.
func printpanics(p *_panic) {
if p.link != nil {
printpanics(p.link)
print("\t")
}
print("panic: ")
+ // Because of preprintpanics, p.arg cannot be an error or
+ // stringer, so this won't call into user code.
printany(p.arg)
if p.recovered {
print(" [recovered]")
@@ -654,7 +657,7 @@ func recovery(gp *g) {
gogo(&gp.sched)
}
-// startpanic_m implements unrecoverable panic.
+// startpanic_m prepares for an unrecoverable panic.
//
// It can have write barriers because the write barrier explicitly
// ignores writes once dying > 0.
@@ -664,10 +667,12 @@ func startpanic_m() {
_g_ := getg()
if mheap_.cachealloc.size == 0 { // very early
print("runtime: panic before malloc heap initialized\n")
- _g_.m.mallocing = 1 // tell rest of panic not to try to malloc
- } else if _g_.m.mcache == nil { // can happen if called from signal handler or throw
- _g_.m.mcache = allocmcache()
}
+ // Disallow malloc during an unrecoverable panic. A panic
+ // could happen in a signal handler, or in a throw, or inside
+ // malloc itself. We want to catch if an allocation ever does
+ // happen (even if we're not in one of these situations).
+ _g_.m.mallocing++
switch _g_.m.dying {
case 0: