runtime.Goexit
function is called and fully exits in the call.
f0
or f1
will enter its existing phase after it returns normally.
f2
will enter its exiting phase after the divided-by-zero panic happens.
f3
will enter its exiting phase after the runtime.Goexit
function call fully exits.
import (
"fmt"
"runtime"
)
func f0() int {
var x = 1
defer fmt.Println("exits normally:", x)
x++
return x
}
func f1() {
var x = 1
defer fmt.Println("exits normally:", x)
x++
}
func f2() {
var x, y = 1, 0
defer fmt.Println("exits for panicking:", x)
x = x / y // will panic
x++ // unreachable
}
func f3() int {
x := 1
defer fmt.Println("exits for Goexiting:", x)
x++
runtime.Goexit()
return x+x // unreachable
}
runtime.Goexit()
function is not intended to be called in the main goroutine of a program.
runtime.Goexit
function is called in a function call, we say a Goexit signal starts associating with the function call after the runtime.Goexit
call fully exits. As explained in the last section, associating either a panic or a Goexit signal with a function call will make the function call enter its exiting phase immediately.
main
function call.
package main
import "fmt"
func main() {
defer func() {
fmt.Println(recover()) // 3
}()
defer panic(3) // will replace panic 2
defer panic(2) // will replace panic 1
defer panic(1) // will replace panic 0
panic(0)
}
runtime.Goexit
function might be called later in the process of executing the call, so panics and Goexit signals might associate with the call later.
package main
func main() {
// The new goroutine.
go func() {
// This is an anonymous deferred call.
// When it fully exits, the panic 2 will spread
// to the entry function call of the new
// goroutine, and replace the panic 0. The
// panic 2 will never be recovered.
defer func() {
// As explained in the last example,
// panic 2 will replace panic 1.
defer panic(2)
// When the anonymous function call fully
// exits, panic 1 will spread to (and
// associate with) the nesting anonymous
// deferred call.
func () {
// Once the panic 1 occurs, there will
// be two unrecovered panics coexisting
// in the new goroutine. One (panic 0)
// associates with the entry function
// call of the new goroutine, the other
// (panic 1) associates with the
// current anonymous function call.
panic(1)
}()
}()
panic(0)
}()
select{}
}
panic: 0 panic: 1 panic: 2 ...
<nil>
, because the bye
panic will be recovered by the Goexit signal.
package main
import (
"fmt"
"runtime"
)
func f() {
defer func() {
fmt.Println(recover())
}()
// The Goexit signal will disable the "bye" panic.
defer runtime.Goexit()
panic("bye")
}
func main() {
go f()
for runtime.NumGoroutine() > 1 {
runtime.Gosched()
}
}
recover
Calls Are No-Ops
recover
function must be called at proper places to take effect. Otherwise, the calls are no-ops. For example, none of the recover
calls in the following example recover the bye
panic.
package main
func main() {
defer func() {
defer func() {
recover() // no-op
}()
}()
defer func() {
func() {
recover() // no-op
}()
}()
func() {
defer func() {
recover() // no-op
}()
}()
func() {
defer recover() // no-op
}()
func() {
recover() // no-op
}()
recover() // no-op
defer recover() // no-op
panic("bye")
}
recover
call takes effect.
package main
func main() {
defer func() {
recover() // take effect
}()
panic("bye")
}
recover
calls in the first example of the current section take effect? Let's read the current version of Go specification:
recover
is nil
if any of the following conditions holds:
panic
's argument was nil;
recover
was not called directly by a deferred function.
recover
calls in the first example of the current section satisfy either the second or the third conditions mentioned in Go specification, except the first call. Yes, here, the current descriptions are not precise yet. The third condition should be described as
recover
was not called directly by a deferred function call which was called directly by the function call associating with the expected to-be-recovered panic.
main
function call. The first recover
call is called directly by a deferred function call but the deferred function call is not called directly by the main
function call. This is why the first recover
call is a no-op.
recover
call (by code line order), which is expected to recover panic 1, in the following example doesn't take effect.
// This program exits without recovering panic 1.
package main
func demo() {
defer func() {
defer func() {
recover() // this one recovers panic 2
}()
defer recover() // no-op
panic(2)
}()
panic(1)
}
func main() {
demo()
}
recover
call is viewed as an attempt to recover the newest unrecovered panic in the current goroutine.
recover
call in the above example attempts to recover the newest unrecovered panic, panic 2, which is associating with the caller call of the second recover
call. The second recover
call is not called directly by a deferred function call which is called by the associating function call. Instead, it is directly called by the associating function call. This is why the second recover
call is a no-op.
recover
calls will take effect:
recover
call takes effect only if the direct caller of the recover
call is a deferred call and the direct caller of the deferred call associates with the newest unrecovered panic in the current goroutine. An effective 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 unrecovered panic.
The Go 101 project is hosted on Github. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.
If you would like to learn some Go details and facts every serveral days, please follow Go 101's official Twitter account @zigo_101.
reflect
standard package.sync
standard package.sync/atomic
standard package.