1
0
mirror of https://github.com/golang/go synced 2024-09-29 14:24:32 -06:00

runtime: simply user throws, expand runtime throws

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>
This commit is contained in:
Michael Pratt 2022-03-07 14:07:14 -05:00
parent 29bbca5c2c
commit 4289bd365c
9 changed files with 49 additions and 18 deletions

View File

@ -93,8 +93,12 @@ messages are prefixed with "runtime:".
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
===============

View File

@ -120,7 +120,7 @@ func sighandler(_ureg *ureg, note *byte, gp *g) int {
return _NCONT
}
Throw:
_g_.m.throwing = 1
_g_.m.throwing = throwTypeRuntime
_g_.m.caughtsig.set(gp)
startpanic_m()
print(notestr, "\n")

View File

@ -11,6 +11,28 @@ import (
"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
@ -1003,13 +1025,16 @@ func throw(s string) {
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
@ -1018,7 +1043,7 @@ func fatal(s string) {
print("fatal error: ", s, "\n")
})
fatalthrow()
fatalthrow(throwTypeUser)
}
// runningPanicDefers is non-zero while running deferred functions for panic.
@ -1063,13 +1088,13 @@ func recovery(gp *g) {
// 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
@ -1216,7 +1241,7 @@ func dopanic_m(gp *g, pc, sp uintptr) bool {
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)
}
@ -1258,7 +1283,7 @@ func canpanic(gp *g) bool {
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)

View File

@ -4088,7 +4088,6 @@ func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
_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
@ -5045,7 +5044,6 @@ func checkdead() {
}
}
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!")
}

View File

@ -38,9 +38,13 @@ func gotraceback() (level int32, all, crash bool) {
_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)
}

View File

@ -532,7 +532,7 @@ type m struct {
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

View File

@ -698,7 +698,7 @@ func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) {
return
}
_g_.m.throwing = 1
_g_.m.throwing = throwTypeRuntime
_g_.m.caughtsig.set(gp)
if crashing == 0 {

View File

@ -226,7 +226,7 @@ func winthrow(info *exceptionrecord, r *context, gp *g) {
}
print("\n")
_g_.m.throwing = 1
_g_.m.throwing = throwTypeRuntime
_g_.m.caughtsig.set(gp)
level, _, docrash := gotraceback()

View File

@ -447,7 +447,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
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")
@ -913,7 +913,7 @@ func gcallers(gp *g, skip int, pcbuf []uintptr) int {
// 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)