Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it okay to panic inside defer function, especially when it's already panicking?

Tags:

go

func sub(){
    defer func (){
        panic(2)
    }()
    panic(1)
}

func main(){
    defer func(){
        x:=recover()
        println(x.(int));
    }()
    sub()
}

I tried this code and it seems the first panic panic(1) is simply "overwritten" by the second panic panic(2).

But is it okay to do that? Or call a Golang function that might panic inside defer function?

(In C++ it's almost never acceptable to throw exception out of a destructor. It terminates program if stack is already unwinding. I wonder if panicking in a similar manner could be bad in Golang.)

like image 411
cshu Avatar asked Dec 14 '16 09:12

cshu


People also ask

Does defer run after panic?

Using Defer with Panic In this case, the function exits immediately, and doesn't execute any statement after the panic function call. In fact, we can even find out if panic was called and prevent the program from exiting using the in-built recover function.

What happens if a Go routine panics?

The mechanism is called panic/recover. We can call the built-in panic function to create a panic to make the current goroutine enter panicking status. Panicking is another way to make a function return. Once a panic occurs in a function call, the function call returns immediately and enters its exiting phase.

What is defer function Go?

In Go language, defer statements delay the execution of the function or method or an anonymous method until the nearby functions returns. In other words, defer function or method call arguments evaluate instantly, but they don't execute until the nearby functions returns.

Which built-in function stops the ordinary flow of control and begins panicking?

Panic is a built-in function that stops the ordinary flow of control and begins panicking. When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller.


1 Answers

Yes, it's okay. Panicking from a deferred function is not really a new, special state, it just means that the panicking sequence will not stop.

Your example code also proves that it's okay, and even a panic() called from a deferred function can be stopped by an "upper" level call to recover().

Spec: Handling panics:

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.

One thing to note here is that even if you call panic() in a deferred function, still all the other deferred functions will run. Also a panic() without recover() from a deferred function will rather "wrap" the existing panic and not "overwrite" it (although it's true that a recover() call will only give you back the value passed to the last panic() call).

See this example:

func main() {
    defer func() {
        fmt.Println("Checkpoint 1")
        panic(1)
    }()
    defer func() {
        fmt.Println("Checkpoint 2")
        panic(2)
    }()
    panic(999)
}

Output (try it on the Go Playground):

Checkpoint 2
Checkpoint 1
panic: 999
    panic: 2
    panic: 1

goroutine 1 [running]:
panic(0xfed00, 0x1040e140)
    /usr/local/go/src/runtime/panic.go:500 +0x720
main.main.func1()
    /tmp/sandbox284410661/main.go:8 +0x120
panic(0xfed00, 0x1040e0fc)
    /usr/local/go/src/runtime/panic.go:458 +0x8a0
main.main.func2()
    /tmp/sandbox284410661/main.go:12 +0x120
panic(0xfed00, 0x1040e0f8)
    /usr/local/go/src/runtime/panic.go:458 +0x8a0
main.main()
    /tmp/sandbox284410661/main.go:14 +0xc0

Even though all deferred functions call panic(), all deferred functions get executed, and the final panic sequence printed contains values passed to all panic() calls.

If you call recover() in the deferred functions, you also get this "recovered" state or info in the final printout:

defer func() {
    recover()
    fmt.Println("Checkpoint 1")
    panic(1)
}()
defer func() {
    recover()
    fmt.Println("Checkpoint 2")
    panic(2)
}()

Output (try it on the Go Playground):

Checkpoint 2
Checkpoint 1
panic: 999 [recovered]
    panic: 2 [recovered]
    panic: 1
...
like image 110
icza Avatar answered Oct 07 '22 14:10

icza