7

I used to think the panic in a goroutine will kill the program if its caller finishes before the panic (the deferred recovering gives no help since at that point there's no panic occurs yet),

until I tried following code:



    func fun1() {
        fmt.Println("fun1 started")
        defer func() {
            if err := recover(); err != nil {
                fmt.Println("recover in func1")
            }
        }()

        go fun2()

        time.Sleep(10 * time.Second) // wait for the boom!
        fmt.Println("fun1 ended")
    }

    func fun2() {
        fmt.Println("fun2 started")

        time.Sleep(5 * time.Second)
        panic("fun2 booom!")

        fmt.Println("fun2 ended")
    }

I found no matter the caller function finishes or not, if the goroutines it starts panic, the caller's deferred recover mechanism will not help. The whole program is still dead.

So, WHY? Theoretically the caller function is still running. When the panics happen the caller's deferred functions should work (including the recovering).

Dave Wu
  • 145
  • 3
  • 8

2 Answers2

11

The specification says:

While executing a function F, an explicit call to panic or a run-time panic terminates the execution of F. Any functions deferred by F are then executed as usual. Next, any deferred functions run by F's caller are run, and so on up to any deferred by the top-level function in the executing goroutine. At that point, the program is terminated and the error condition is reported, including the value of the argument to panic. This termination sequence is called panicking.

Because fun2 is the top-level function executing in the goroutine and fun2 does not recover from a panic, the program terminates when fun2 panics.

The deferred call in fun1 is not called when the goroutine executing fun2 panics.

A goroutine cannot recover from a panic in another goroutine.

Charlie Tumahai
  • 113,709
  • 12
  • 249
  • 242
  • So it means that the caller's deferred recovery IS invoked after panicking in the child goroutine, however the recover() in it cannot get any non-nil err since the reason of **A gotourine cannot recover from another goroutine's panic** --- it makes sense. – Dave Wu Dec 15 '17 at 00:51
  • The deferred function in `fun1` __is not__ called when `fun2` panics. It follows that `fun1` cannot recover from panics in `fun2`. – Charlie Tumahai Dec 15 '17 at 01:22
  • 1
    But the specification just mentioned: _Next, any deferred functions run by F's caller are run_ – Dave Wu Dec 15 '17 at 01:45
  • _... and so on up to any deferred by the top-level function in the executing goroutine._ – Charlie Tumahai Dec 15 '17 at 03:05
  • @DaveWu I think that `func1` is not `func2`'s caller. `func2` runs in a different go routine. – Viet Tran Jul 13 '18 at 11:03
  • +1 @VietTran, The `func1` and `func2` goroutines are running at the same level, hence when either panics, _unwinding of stack of the goroutine_ happens, and if it reaches the top of the goroutine, the entire program _dies_. So in your example, `func2` panics and the runtime fails the entire program. – Krishna Gupta Jun 29 '21 at 00:53
1

Instead of recovering in fun1() you can use runtime.Goexit() in fun2() which will

Goexit terminates the goroutine that calls it. No other goroutine is affected.

Something like

func fun2() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Do some cleanup and teardown")
            runtime.Goexit() //Here
        }
    }
    ...
}
Uvelichitel
  • 8,220
  • 1
  • 19
  • 36
  • Note, however, that this will prevent deferred calls from running in `fun2()`'s caller, even if they are in the same goroutine. For example, if the call path instead was `fun1()` -> `go fun3()` -> `fun2()`, then calling `runtime.Goexit()` in `fun2()`, even in a deferred recovery, will prevent any deferred calls in `fun3()` from running. – Kaedys Dec 14 '17 at 17:58