Golang, the right way to restart a routine that has panicked

I have the following code example. I want to support 4 goroutines all the time. They have the opportunity to panic. In case of panic, I have a recovery where I restart goroutine.

The way I implemented the work, but I'm not sure if he did it right. 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) } 
+5
source share
4 answers

You can extract recovery logic in a function, for example:

 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) }) 
+3
source

Oh, I’m not saying that the following is more correct than your path. This is just another way to do this.

Create another function, name it printerRecover or something like that, and set it aside / restore there. Then in the printer simply proceed to calling printerRecover . Add functions to the return values ​​to check if you need goroutine to exit for any reason.

+1
source

As the Zan Lynx answer, I would like to share another way to do this (although this is pretty much like the OP path.) I used the extra buffer channel ch . When goroutine panics, the recovery function inside goroutine sends the identifier i to ch . In for the loop at the bottom of main() , it detects which goroutine is in a panic, and whether to restart it, getting values ​​from ch .

Run on the 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) } } 
+1
source

The correct option. Only for me, the approach to maintaining exactly 4 routines executed at all times does not look like much go_way, either it processes the processing identifier or spawns in a delay, which can lead to an unpredictable stack due to closing. I do not think you can balance resources in this way effectively. Why don't you like a simple spawning worker when you need him?

 func main() { ... go func(tasks chan int){ //multiplexer for { task = <-tasks //when needed go printer(task) //just spawns handler } }(ch) ... } 

and let runtime do its job? Thus, everything is done in stdlib listeners / servers, and they are known as quite efficient. The goroutines are very easy to appear, and the running time is pretty smart to balance the load. Of course, you must recover anyway. It's my personal opinion.

0
source

Source: https://habr.com/ru/post/1262665/


All Articles