Panic & Recover
About 4 min
Panic & Recover | Exception Handling
In Golang, panic
and recover
are two keywords used for exception handling. panic
is used to raise an exception, while recover
is used to catch exceptions.
Tips
Function | Description |
---|---|
panic(interface{}) | Raises an exception, stops the execution of the current Goroutine, and recursively executes the defer methods within the current Goroutine. |
recover() interface{} | Catches an exception, returns the exception information, and is used to handle exceptions, preventing program crashes due to exceptions. It can only be called within a defer block. |
Panic Data Structure
The data structure for panic
is defined in the runtime._panic
struct:
// _panic stores information about an active panic.
//
// _panic values can only exist on the stack.
//
// argp and link fields are stack pointers, but they do not need special handling during stack growth:
// because they are pointer types and _panic values only exist on the stack, regular stack pointer adjustments handle them.
type _panic struct {
argp unsafe.Pointer // Points to the arguments of deferred calls that ran during the panic; cannot move - known to liblink
arg any // The argument to panic
link *_panic // Points to the link of the previous panic
// startPC and startSP track where _panic.start was called.
startPC uintptr
startSP unsafe.Pointer
// We're running the current stack frame of a deferred call.
sp unsafe.Pointer
lr uintptr
fp unsafe.Pointer
// If the function returned by _panic.next() recovers the panic, retpc stores the PC to jump back to.
retpc uintptr
// Additional state for handling inlined defers.
deferBitsPtr *uint8
slotsPtr unsafe.Pointer
recovered bool // Whether this panic has been recovered
goexit bool
deferreturn bool
}
- The
_panic
struct is used to store information about an active panic. _panic
values can only exist on the stack and are not allocated on the heap.- The
argp
andlink
fields are stack pointers, but they do not require special handling during stack growth. Because they are pointer types and_panic
values only exist on the stack, regular stack pointer adjustments handle them. startPC
andstartSP
track where_panic.start
was called.sp
points to the current stack frame of a deferred call.retpc
stores the PC to jump back to if the function returned by_panic.next()
recovers the panic.recovered
indicates whether this panic has been recovered.
Scope of `panic`
panic
only triggers thedefer
within the current Goroutine.
package main
import (
"fmt"
"time"
)
func main() {
for i := range 4 {
go testPanic(i)
}
time.Sleep(time.Second)
}
func testPanic(i int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("recover panic: ", i)
}
}()
if i == 1 {
panic(fmt.Sprintf("panic %d", i))
}
fmt.Println("test panic: ", i)
}
// Result
// test panic: 0
// recover panic: 1
// test panic: 3
// test panic: 2
recover
is used to capture exceptions, return exception information, and handle exceptions to prevent program crashes caused by exceptions. It is only valid when called indefer
, and calling elsewhere will only returnnil
.panic
can also be called indefer
package main
import (
"fmt"
"time"
)
func main() {
for i := range 4 {
go testPanic(i)
}
time.Sleep(time.Second)
}
func testPanic(i int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("recover panic: ", i)
panic("panic in defer")
}
}()
if i == 1 {
panic(fmt.Sprintf("panic %d", i))
}
fmt.Println("test panic: ", i)
}
// Result
// test panic: 3
// test panic: 0
// test panic: 2
// recover panic: 1
// panic: panic 1 [recovered]
// panic: panic in defer
Execution Flow of Panic
Execution Flow
- The compiler translates
panic
into a call to thegopanic
function in theruntime
package. - The
defer
chain for the Goroutine is executed in reverse order. - If there is no
recover
in thedefer
, the code within thedefer
is executed. - If there is a
recover
in thedefer
, theruntime.gorecover
function is called. It sets therecovered
field in thepanic
totrue
, retrieves the program counter (pc
) and stack pointer (sp
) fromruntime._defer
, executesruntime.recovery
to recover the program, and finally callsruntime.deferproc
with a return value of1
to indicate successful recovery. - The
deferreturn
field of thepanic
is set totrue
, indicating that thedefer
has completed execution. - If none of the
defer
blocks contain arecover()
, the program terminates by executingruntime.fatalpanic
.
Example
package main
import (
"fmt"
)
func main() {
defer func() {
fmt.Println("func 1")
}()
defer func() {
fmt.Println("func 2")
}()
defer func() {
fmt.Println("func 3")
if r := recover(); r != nil {
fmt.Println("recover")
}
}()
panic("panic")
}
// Output:
// func 3
// recover
// func 2
// func 1
Exception Handling
Difficult-to-Catch Exception Types
The following exceptions cannot be caught using recover
:
- Out of Memory: When pre-allocated memory space is too large and results in an out-of-memory condition, the error message will be
runtime: out of memory
. - Concurrent Map Read and Write: Attempting concurrent read and write operations on a map will result in the error message
concurrent map read and map write
. - Stack Exhaustion: When the stack memory is exhausted, the error message will be
runtime: goroutine stack exceeds 1000000000-byte limit
. - Goroutine Running on NULL Machine: Running a Goroutine on a NULL machine will result in the error message
runtime: goroutine running on NULL machine
. - All Goroutines Asleep (Deadlock): When all Goroutines are asleep and the program is deadlocked, the error message will be
all goroutines are asleep - deadlock!
.
Catchable Exceptions
The following exceptions can be caught using recover
:
- Array Index Out of Range: When an array index is out of range, the error message will be
panic: runtime error: index out of range
. - Nil Pointer Dereference: Attempting to dereference a nil pointer will result in the error message
panic: runtime error: invalid memory address or nil pointer dereference
. - Type Assertion Failure: When a type assertion fails, the error message will be
panic: interface conversion: interface {} is nil, not int
. - Division by Zero: Attempting to divide by zero will result in the error message
panic: runtime error: integer divide by zero
. - Calling an Undefined Method: Calling a method that does not exist will result in the error message
panic: runtime error: invalid memory address or nil pointer dereference
.