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)
}
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 .
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.
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.
# 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.
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 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)
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With