(Update: I wrote a more formal article on this topic.)
At a time, there might be multiple unrecovered panics coexisting in a goroutine. Each one associates with one non-exited function call in the call stack of the goroutine. Each non-exited function call may associate with at most one unrecovered panic.
Similarly, at a time, there might be multiple Goexit
signals coexisting in a goroutine. Each one associates
with one non-exited function call in the call stack of
the goroutine. Each non-exited function call may
associate with at most one Goexit
signal.
Unlike panics, Goexit
signals may not be
cancelled.
A function call associating with an unrecovered panic or
a Goexit
signal is called a panicking call
and must enter (if it hasn't) its terminating execution phase.
In the terminating execution phase of a function call,
any functions deferred
by the panicking call are executed as usual.
A deferred call invoked by a panicking call associates no panics initially and so it is not panicking initially.
When a nested call exits and it is still associating with an unrecovered panic, the unrecovered panic will spread to and associate with the nesting call. That says, if there was an unrecovered panic associating with the nesting call before, it will be replaced by the spread one (as the one associating with the nesting call). So, when a goroutine exits, there may be at most one unrecovered panic in the goroutine. The exiting of a goroutine with an unrecovered panic makes the whole program crash. The information of the unrecovered panic will be reported when the program crashes.
We can think a panic
call creates a panic
internally and associates the panic with the call itself.
Note that some runtime operations, such as divided by integer
0, may make some implicit panic
calls.
The same process applies for Goexit
signals,
execpt that the final Goexit
signal will not
make the program crash.
Each recover
call is viewed as an attempt
to recover the newest unrecovered panic in the currrent
goroutine. The recover succeeds only if the caller of
that recover
call is a deferred call and
the caller of the deferred call associates with the
newest unrecovered panic.
A successful recover
call disassociates the
newest unrecovered panic from its associating function call,
and returns the value passed to the panic
call which produced the newest panic. An unsuccessful
recover
call returns nil and is viewed as
a no-op.
In the function pretect
, the recover
call effectively catches any panic produced in the call to
function g
.
func protect(g func()) { // The recover in this deferred call protects callers // from panics raised by g. defer func() { log.Println("done") if x := recover(); x != nil { log.Printf("run time panic: %v\n", x) } }() log.Println("start") g() }
A call to the function norecover
will exit
with an unrecovered panic.
func norecover() { defer func() { // anonymous function 1 defer func() { // Returns nil because anonymous function 1 // associates with no panics. recover() }() }() // Returns nil because the caller of recover is not // a deferred call called by function norecover. recover() panic("not recovered") }
A call to the function replace
will exit
with the unrecovered panic 4
.
func replace(g func()) { // Panic 4 replaces panic 2. defer func() { // Panic 4 replaces panic 3. defer painc(4) func () { panic(3) }() }() // Panic 2 replaces panic 1. defer panic(2) panic(1) }