Skip to content

Commit 2edc4d4

Browse files
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 9483a0b commit 2edc4d4

File tree

2 files changed

+10
-6
lines changed

2 files changed

+10
-6
lines changed

src/runtime/error.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ func typestring(x interface{}) string {
7272
return e._type.string()
7373
}
7474

75-
// For calling from C.
76-
// Prints an argument passed to panic.
75+
// printany prints an argument passed to panic.
7776
func printany(i interface{}) {
7877
switch v := i.(type) {
7978
case nil:

src/runtime/panic.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,12 +408,15 @@ func preprintpanics(p *_panic) {
408408
}
409409

410410
// Print all currently active panics. Used when crashing.
411+
// Should only be called after preprintpanics.
411412
func printpanics(p *_panic) {
412413
if p.link != nil {
413414
printpanics(p.link)
414415
print("\t")
415416
}
416417
print("panic: ")
418+
// Because of preprintpanics, p.arg cannot be an error or
419+
// stringer, so this won't call into user code.
417420
printany(p.arg)
418421
if p.recovered {
419422
print(" [recovered]")
@@ -654,7 +657,7 @@ func recovery(gp *g) {
654657
gogo(&gp.sched)
655658
}
656659

657-
// startpanic_m implements unrecoverable panic.
660+
// startpanic_m prepares for an unrecoverable panic.
658661
//
659662
// It can have write barriers because the write barrier explicitly
660663
// ignores writes once dying > 0.
@@ -664,10 +667,12 @@ func startpanic_m() {
664667
_g_ := getg()
665668
if mheap_.cachealloc.size == 0 { // very early
666669
print("runtime: panic before malloc heap initialized\n")
667-
_g_.m.mallocing = 1 // tell rest of panic not to try to malloc
668-
} else if _g_.m.mcache == nil { // can happen if called from signal handler or throw
669-
_g_.m.mcache = allocmcache()
670670
}
671+
// Disallow malloc during an unrecoverable panic. A panic
672+
// could happen in a signal handler, or in a throw, or inside
673+
// malloc itself. We want to catch if an allocation ever does
674+
// happen (even if we're not in one of these situations).
675+
_g_.m.mallocing++
671676

672677
switch _g_.m.dying {
673678
case 0:

0 commit comments

Comments
 (0)