How to catch a runtime error from a function called from a waitgroup?

How to handle handouts effectively in waitgroup?

In other words, in the following code snippet, how to catch the panic / crush method of calling gorourines do() ?

 func do(){ str := "abc" fmt.Print(str[3]) defer func() { if err := recover(); err != nil { fmt.Print(err) } }() } func main() { var wg sync.WaitGroup for i := 0; i < 1; i++ { wg.Add(1) go do() defer func() { wg.Done() if err := recover(); err != nil { fmt.Print(err) } }() } wg.Wait() fmt.Println("This line should be printed after all those invocations fail.") } 
+5
source share
2 answers

Firstly, registering a delayed function for recovery should be the first line in the function, because since you do this last, it will not even be reached, because the line / code before defer already panicking, and therefore the delayed function is not registered, which restores the pan state.

So, change your do() function to the following:

 func do() { defer func() { if err := recover(); err != nil { fmt.Println("Restored:", err) } }() str := "abc" fmt.Print(str[3]) } 

Secondly: this alone will not make your code work, since you call wg.Defer() in a deferred function that will be executed only once main() ends - it never happens because you call wg.Wait() in main() . Thus, wg.Wait() expects wg.Wait() calls, but wg.Done() calls will not be made until wg.Wait() returns. This is a dead end.

You should call wg.Done() from the do() function, in the deferred function, something like this:

 var wg sync.WaitGroup func do() { defer func() { if err := recover(); err != nil { fmt.Println(err) } wg.Done() }() str := "abc" fmt.Print(str[3]) } func main() { for i := 0; i < 1; i++ { wg.Add(1) go do() } wg.Wait() fmt.Println("This line should be printed after all those invocations fail.") } 

Conclusion (try on the Go Playground ):

 Restored: runtime error: index out of range This line should be printed after all those invocations fail. 

This, of course, needed to move the wg variable to the global scope. Another option is to pass it to do() as an argument. If you decide to go this route, note that you need to pass a pointer to WaitGroup , otherwise only a copy will be passed ( WaitGroup is a type of struct ), and calling WaitGroup.Done() in the copy will not affect the original.

With passing WaitGroup to do() :

 func do(wg *sync.WaitGroup) { defer func() { if err := recover(); err != nil { fmt.Println("Restored:", err) } wg.Done() }() str := "abc" fmt.Print(str[3]) } func main() { var wg sync.WaitGroup for i := 0; i < 1; i++ { wg.Add(1) go do(&wg) } wg.Wait() fmt.Println("This line should be printed after all those invocations fail.") } 

The output is the same. Try this option on the Go Playground .

+3
source

@icza did a fantastic job explaining how to use WaitGroup and its Wait and Done functions correctly.

I like WaitGroup simplicity. However, I don’t like that we need to pass the link to goroutine, because it will mean that the concurrency logic will be mixed with your business logic.

So, I came up with this general function to solve this problem for me:

 // Parallelize parallelizes the function calls func Parallelize(functions ...func()) { var waitGroup sync.WaitGroup waitGroup.Add(len(functions)) defer waitGroup.Wait() for _, function := range functions { go func(copy func()) { defer waitGroup.Done() copy() }(function) } } 

So your example can be solved as follows:

 func do() { defer func() { if err := recover(); err != nil { fmt.Println(err) } }() str := "abc" fmt.Print(str[3]) } func main() { Parallelize(do, do, do) fmt.Println("This line should be printed after all those invocations fail.") } 

If you want to use it, you can find it here https://github.com/shomali11/util

0
source

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


All Articles