Recover is a built-in function that regains control of a panicking goroutine. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.
func f() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered in f", r) } }() fmt.Println("Calling g.") panic("oops") fmt.Println("Returned normally from g.") }
Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect.
https://go.dev/blog/defer-panic-and-recover
Why?
Firstly, we need to investigate what happens when we call recover(). When using recover(), the compiler will compile this statement into runtime.gorecover(). Let’s see the source code of this function:
func gorecover(argp uintptr) any { // Must be in a function running as part of a deferred call during the panic. // Must be called from the topmost function of the call // (the function used in the defer statement). // p.argp is the argument pointer of that topmost deferred function call. // Compare against argp reported by caller. // If they match, the caller is the one who can recover. gp := getg() p := gp._panic if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) { p.recovered = true return p.arg } return nil }
https://github.com/golang/go/blob/master/src/runtime/panic.go#L1038
Let’s analyze:
The program takes the pointer to the current goroutine via getg(). Pay attention to the attribute _panic. One of the conditions for recover() to be executed is that the _panic must be not nil. In the same way, _panic of current gorountine must be not nil.
func gopanic(e interface{}) { gp := getg() ... var p _panic p.link = gp._panic gp._panic = (*_panic)(noescape(unsafe.Pointer(&p))) ... }
We can see _panic is manipulated inside runtime.gopanic(), this is the function that is executed when we call panic(). So, if the current goroutine does not panic, its _panic is always nil. Then no matter how many times we call recover(), it will always return nil and do nothing.
Remind panic() function
When panic()
is called, the program stops executing the current function immediately and begins unwinding the stack, which means it exits from the current function and goes up through the call stack, running deferred functions along the way. Before the program completely stops, any deferred functions in the stack (functions that were scheduled to run using defer
) are executed
Conclusion
References
https://groups.google.com/g/golang-nuts/c/KgPOzaMylHo/m/zX0GosvnAQAJ
https://github.com/golang/go/blob/master/src/runtime/panic.go#L1038
https://www.sobyte.net/post/2022-01/go-source-code-panic-and-recover/