Order of recovery

September 24, 2024

Yesterday we saw how panic plays with deferred functions, in terms of order of execution.

Today we’ll take things up one level, and throw recover in there… there’s more to recover than just execution order. We’ll get to that next.

Handling panics

The recover function allows a program to manage behavior of a panicking goroutine. Suppose a function G defers a function D that calls recover and a panic occurs in a function on the same goroutine in which G is executing. When the running of deferred functions reaches D, the return value of D’s call to recover will be the value passed to the call of panic. If D returns normally, without starting a new panic, the panicking sequence stops. In that case, the state of functions called between G and the call to panic is discarded, and normal execution resumes. Any functions deferred by G before D are then run and G’s execution terminates by returning to its caller.

Let’s demonstrate this order of execution by expanding on yesterday’s code example:

func main() {
	foo()
}

func foo() {
	defer fmt.Println("foo is done")
	bar()
}

func bar() {
	defer fmt.Println("bar is done")
	baz()
}

func baz() {
	defer fmt.Println("baz is done")
	panic(123)
}

As written, this code outputs:

baz is done
bar is done
foo is done
panic: 123

followed by a stack trace.

Let’s now add a recovery, and see how this affect things.

func main() {
	foo()
}

func foo() {
	defer fmt.Println("foo is done")
	bar()
}

func bar() {
	defer fmt.Println("bar is done")
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("recovered", r)
		}
	}()
	defer fmt.Println("an extra defer for good measure")
	baz()
}

func baz() {
	defer fmt.Println("baz is done")
	panic(123)
}

I also threw in an extra defer for good measure. 😉 Run the code yourself to see that now the output is as follows:

baz is done
an extra defer for good measure
recovered 123
bar is done
foo is done

Two things to point out here. First, and most important, there’s no panic! The panic was recovered, and execution was permitted to continue as normal from the recovery point, without the program exiting. This is one other thing that functions like log.Fatal, doesn’t allow—there’s no opportunity to recover from a log.Fatal!

Second, notice the order of the deferred statement executions. They execute in the opposite order declared, even considering the recovery.

Quotes from The Go Programming Language Specification Language version go1.23 (June 13, 2024)


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Get daily content like this in your inbox!

Subscribe