17

I understand that to handle panic recover is used. But the following block fails to recover when panic arises in go routine

func main() {
    done := make(chan int64)
    defer fmt.Println("Graceful End of program")
    defer func() {
     r := recover()
     if _, ok := r.(error); ok {
        fmt.Println("Recovered")
     }
    }()

    go handle(done)
    for {
        select{
        case <- done:
        return
        }
    } 
}

func handle(done chan int64) {
    var a *int64
    a = nil

    fmt.Println(*a)
    done <- *a
}

However following block is able to execute as expected

func main() {
    done := make(chan int64)
    defer fmt.Println("Graceful End of program")
    defer func() {
     r := recover()
     if _, ok := r.(error); ok {
        fmt.Println("Recovered")
     }
    }()

    handle(done)
    for {
        select{
        case <- done:
        return
        }
    } 
}

func handle(done chan int64) {
    var a *int64
    a = nil

    fmt.Println(*a)
    done <- *a
}

How to recover from panics that arise in go routines. Here is the link for playground : https://play.golang.org/p/lkvKUxMHjhi

Mohit Jain
  • 733
  • 3
  • 9
  • 24

2 Answers2

37

Recover only works when called from the same goroutine as the panic is called in. From the Go blog:

The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes

You would have to have a deferred recover within the goroutine.

https://blog.golang.org/defer-panic-and-recover

The docs / spec also includes the same :

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

https://golang.org/ref/spec#Handling_panics

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Andrew
  • 26,629
  • 5
  • 63
  • 86
1

I use the following way to handle this case, and it works as expected.

package main

import (
    "fmt"
    "time"
)

func main() {
    defer fmt.Println("Graceful End of program")
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered:", r)
        }
    }()

    done := make(chan int64)
    var panicVar interface{}

    go handle(done, &panicVar)

WAIT:
    for {
        select {
        case <-done:
            break WAIT // break WAIT: goroutine exit normally
        default:
            if panicVar != nil {
                break WAIT // break WAIT: goroutine exit panicked
            }

            // wait for goroutine exit
        }

        time.Sleep(1 * time.Microsecond)
    }

    if panicVar != nil {
        panic(panicVar) // panic again
    }
}

func handle(done chan int64, panicVar *interface{}) {
    defer func() {
        if r := recover(); r != nil {
            // pass panic variable outside
            *panicVar = r
        }
    }()

    var a *int64
    a = nil

    fmt.Println(*a)
    done <- *a
}

Playground link: https://play.golang.org/p/t0wXwB02pa3

xia xiongjun
  • 141
  • 3
  • 6
  • instead of using var to store panic state, use channels. This way you can remove the sleep & for in main. select alone can serve in this case. – lokanadham100 Jul 22 '21 at 13:58