Skip to content

Instantly share code, notes, and snippets.

@zigo101
Last active November 12, 2019 14:40
Show Gist options
  • Save zigo101/e3c01e6ca8794cf9604f55f943232b63 to your computer and use it in GitHub Desktop.
Save zigo101/e3c01e6ca8794cf9604f55f943232b63 to your computer and use it in GitHub Desktop.
Panic.recover mechanism

(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)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment