Handling panics in go routines
Asked Answered
S

2

22

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

Stealth answered 18/5, 2018 at 10:14 Comment(0)
E
43

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

Eri answered 18/5, 2018 at 10:21 Comment(1)
So simply adding a recover in main.go will not help. We first must know in which user created go routine panic happened and add recover there?Typewriting
I
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

Iridectomy answered 3/6, 2021 at 11:37 Comment(1)
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.Lacto

© 2022 - 2024 — McMap. All rights reserved.