Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang, proper way to restart a routine that panicked

Tags:

go

I have the following sample code. I want to maintain 4 goroutines running at all times. They have the possibility of panicking. In the case of the panic, I have a recover where I restart the goroutine.

The way I implemented works but I am not sure whether its the correct and proper way to do this. Any thoughts

package main

import (
    "fmt"
    "time"
)

var gVar string
var pCount int

func pinger(c chan int) {
    for i := 0; ; i++ {
        fmt.Println("adding ", i)
        c <- i
    }
}

func printer(id int, c chan int) {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("HERE", id)
            fmt.Println(err)
            pCount++
            if pCount == 5 {
                panic("TOO MANY PANICS")
            } else {
                go printer(id, c)
            }
        }
    }()

    for {
        msg := <-c
        fmt.Println(id, "- ping", msg, gVar)
        if msg%5 == 0 {
            panic("PANIC")
        }

        time.Sleep(time.Second * 1)

    }
}

func main() {
    var c chan int = make(chan int, 2)
    gVar = "Preflight"
    pCount = 0

    go pinger(c)
    go printer(1, c)
    go printer(2, c)
    go printer(3, c)
    go printer(4, c)

    var input string
    fmt.Scanln(&input)
}
like image 298
Sakib Avatar asked Jan 12 '17 00:01

Sakib


People also ask

How do you deal with panic Goroutine?

Recover and catch a panicThe built-in recover function can be used to regain control of a panicking goroutine and resume normal execution. A call to recover stops the unwinding and returns the argument passed to panic . If the goroutine is not panicking, recover returns nil .

What happens when Goroutine 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.

How do you handle panic errors?

By comparing the err variable against nil , we can detect if a panic occurred, and in this case we log the panic using the log. Println function, as though it were any other error . Following this deferred anonymous function, we call another function that we defined, divide , and attempt to print its results using fmt.

How do I reset Goroutine?

# You can do that by setting a defer with a recover on your goroutine which would write to a channel signaling the death of the goroutine. Then on the main goroutine, you read from that channel, and whenever something is read, you restart a goroutine.


3 Answers

You can extract the recover logic in a function such as:

func recoverer(maxPanics, id int, f func()) {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("HERE", id)
            fmt.Println(err)
            if maxPanics == 0 {
                panic("TOO MANY PANICS")
            } else {
                go recoverer(maxPanics-1, id, f)
            }
        }
    }()
    f()
}

And then use it like:

go recoverer(5, 1, func() { printer(1, c) })
like image 151
Ankur Avatar answered Nov 10 '22 08:11

Ankur


Like Zan Lynx's answer, I'd like to share another way to do it (although it's pretty much similar to OP's way.) I used an additional buffered channel ch. When a goroutine panics, the recovery function inside the goroutine send its identity i to ch. In for loop at the bottom of main(), it detects which goroutine's in panic and whether to restart by receiving values from ch.

Run in Go Playground

package main

import (
    "fmt"
    "time"
)

func main() {
    var pCount int
    ch := make(chan int, 5)

    f := func(i int) {
        defer func() {
            if err := recover(); err != nil {
                ch <- i
            }
        }()

        fmt.Printf("goroutine f(%v) started\n", i)
        time.Sleep(1000 * time.Millisecond)
        panic("goroutine in panic")
    }

    go f(1)
    go f(2)
    go f(3)
    go f(4)

    for {
        i := <-ch
        pCount++
        if pCount >= 5 {
            fmt.Println("Too many panics")
            break
        }
        fmt.Printf("Detected goroutine f(%v) panic, will restart\n", i)
        f(i)
    }
}
like image 25
philipjkim Avatar answered Nov 10 '22 06:11

philipjkim


Oh, I am not saying that the following is more correct than your way. It is just another way to do it.

Create another function, call it printerRecover or something like it, and do your defer / recover in there. Then in printer just loop on calling printerRecover. Add in function return values to check if you need the goroutine to exit for some reason.

like image 1
Zan Lynx Avatar answered Nov 10 '22 07:11

Zan Lynx