This gives explicit names to the possible states of throwing (-1, 0, 1).
m.throwing is now one of:
throwTypeOff: not throwing, previously == 0
throwTypeUser: user throw, previously == -1
throwTypeRuntime: runtime throw, previously == 1
For runtime throws, we now always include frame metadata and system
goroutines regardless of GOTRACEBACK to aid in debugging the runtime.
For user throws, we no longer include frame metadata or runtime frames,
unless GOTRACEBACK=system or higher.
For #51485.
Change-Id: If252e2377a0b6385ce7756b937929be4273a56c0
Reviewed-on: https://go-review.googlesource.com/c/go/+/390421
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
For unrecoverable errors where user code is expected to be at fault for the
failure (such as racing map writes), use `fatal`.
-For runtime error debugging, it's useful to run with
-`GOTRACEBACK=system` or `GOTRACEBACK=crash`.
+For runtime error debugging, it may be useful to run with `GOTRACEBACK=system`
+or `GOTRACEBACK=crash`. The output of `panic` and `fatal` is as described by
+`GOTRACEBACK`. The output of `throw` always includes runtime frames, metadata
+and all goroutines regardless of `GOTRACEBACK` (i.e., equivalent to
+`GOTRACEBACK=system). Whether `throw` crashes or not is still controlled by
+`GOTRACEBACK`.
Synchronization
===============
return _NCONT
}
Throw:
- _g_.m.throwing = 1
+ _g_.m.throwing = throwTypeRuntime
_g_.m.caughtsig.set(gp)
startpanic_m()
print(notestr, "\n")
"unsafe"
)
+// throwType indicates the current type of ongoing throw, which affects the
+// amount of detail printed to stderr. Higher values include more detail.
+type throwType uint32
+
+const (
+ // throwTypeNone means that we are not throwing.
+ throwTypeNone throwType = iota
+
+ // throwTypeUser is a throw due to a problem with the application.
+ //
+ // These throws do not include runtime frames, system goroutines, or
+ // frame metadata.
+ throwTypeUser
+
+ // throwTypeRuntime is a throw due to a problem with Go itself.
+ //
+ // These throws include as much information as possible to aid in
+ // debugging the runtime, including runtime frames, system goroutines,
+ // and frame metadata.
+ throwTypeRuntime
+)
+
// We have two different ways of doing defers. The older way involves creating a
// defer record at the time that a defer statement is executing and adding it to a
// defer chain. This chain is inspected by the deferreturn call at all function
print("fatal error: ", s, "\n")
})
- fatalthrow()
+ fatalthrow(throwTypeRuntime)
}
// fatal triggers a fatal error that dumps a stack trace and exits.
//
// fatal is equivalent to throw, but is used when user code is expected to be
// at fault for the failure, such as racing map writes.
+//
+// fatal does not include runtime frames, system goroutines, or frame metadata
+// (fp, sp, pc) in the stack trace unless GOTRACEBACK=system or higher.
//go:nosplit
func fatal(s string) {
// Everything fatal does should be recursively nosplit so it
print("fatal error: ", s, "\n")
})
- fatalthrow()
+ fatalthrow(throwTypeUser)
}
// runningPanicDefers is non-zero while running deferred functions for panic.
// process.
//
//go:nosplit
-func fatalthrow() {
+func fatalthrow(t throwType) {
pc := getcallerpc()
sp := getcallersp()
gp := getg()
- if gp.m.throwing == 0 {
- gp.m.throwing = 1
+ if gp.m.throwing == throwTypeNone {
+ gp.m.throwing = t
}
// Switch to the system stack to avoid any stack growth, which may make
print("\n")
goroutineheader(gp)
traceback(pc, sp, 0, gp)
- } else if level >= 2 || _g_.m.throwing > 0 {
+ } else if level >= 2 || _g_.m.throwing >= throwTypeRuntime {
print("\nruntime stack:\n")
traceback(pc, sp, 0, gp)
}
if gp == nil || gp != mp.curg {
return false
}
- if mp.locks != 0 || mp.mallocing != 0 || mp.throwing != 0 || mp.preemptoff != "" || mp.dying != 0 {
+ if mp.locks != 0 || mp.mallocing != 0 || mp.throwing != throwTypeNone || mp.preemptoff != "" || mp.dying != 0 {
return false
}
status := readgstatus(gp)
_g_ := getg()
if fn == nil {
- _g_.m.throwing = -1 // do not dump full stacks
fatal("go of nil func value")
}
acquirem() // disable preemption because it can be holding p in a local var
}
}
- getg().m.throwing = -1 // do not dump full stacks
unlock(&sched.lock) // unlock so that GODEBUG=scheddetail=1 doesn't hang
fatal("all goroutines are asleep - deadlock!")
}
_g_ := getg()
t := atomic.Load(&traceback_cache)
crash = t&tracebackCrash != 0
- all = _g_.m.throwing > 0 || t&tracebackAll != 0
+ all = _g_.m.throwing >= throwTypeUser || t&tracebackAll != 0
if _g_.m.traceback != 0 {
level = int32(_g_.m.traceback)
+ } else if _g_.m.throwing >= throwTypeRuntime {
+ // Always include runtime frames in runtime throws unless
+ // otherwise overridden by m.traceback.
+ level = 2
} else {
level = int32(t >> tracebackShift)
}
oldp puintptr // the p that was attached before executing a syscall
id int64
mallocing int32
- throwing int32
+ throwing throwType
preemptoff string // if != "", keep curg running on this m
locks int32
dying int32
return
}
- _g_.m.throwing = 1
+ _g_.m.throwing = throwTypeRuntime
_g_.m.caughtsig.set(gp)
if crashing == 0 {
}
print("\n")
- _g_.m.throwing = 1
+ _g_.m.throwing = throwTypeRuntime
_g_.m.caughtsig.set(gp)
level, _, docrash := gotraceback()
if frame.pc > f.entry() {
print(" +", hex(frame.pc-f.entry()))
}
- if gp.m != nil && gp.m.throwing > 0 && gp == gp.m.curg || level >= 2 {
+ if gp.m != nil && gp.m.throwing >= throwTypeRuntime && gp == gp.m.curg || level >= 2 {
print(" fp=", hex(frame.fp), " sp=", hex(frame.sp), " pc=", hex(frame.pc))
}
print("\n")
// be printed during a traceback.
func showframe(f funcInfo, gp *g, firstFrame bool, funcID, childID funcID) bool {
g := getg()
- if g.m.throwing > 0 && gp != nil && (gp == g.m.curg || gp == g.m.caughtsig.ptr()) {
+ if g.m.throwing >= throwTypeRuntime && gp != nil && (gp == g.m.curg || gp == g.m.caughtsig.ptr()) {
return true
}
return showfuncinfo(f, firstFrame, funcID, childID)