In the following code, how can I add proper timeout error handling in case one of the launched go routines takes too long (e.g. > 10 sec) to finish? Note, that I do not want to have an "overall" timeout but a timeout for each go routine, so that I can also know which go routine timed out in my error report.
var wg sync.WaitGroup
for _, element:= range elements{
wg.Add(1)
go doWork(element, &wg)
}
wg.Wait()
kind regards
nice way is to use context.WithDeadline
:
// WithDeadline returns a copy of the parent context with the deadline adjusted // to be no later than d. If the parent's deadline is already earlier than d, // WithDeadline(parent, d) is semantically equivalent to parent. The returned // context's Done channel is closed when the deadline expires, when the returned // cancel function is called, or when the parent context's Done channel is // closed, whichever happens first. // // Canceling this context releases resources associated with it, so code should // call cancel as soon as the operations running in this Context complete.
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(100*time.Millisecond))
// Even though ctx will be expired, it is good practice to call its
// cancelation function in any case. Failure to do so may keep the
// context and its parent alive longer than necessary.
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
You can use Context, in the following way:
func doWork(ctx context.Context, element Element, wg &sync.WaitGroup) {
defer wg.Done()
done := make(chan struct{})
go func() {
// do some work on element
done <- struct{}{} // signal work is done
}
select {
case <- done:
{
// work completed in time
}
case <- ctx.Done:
{
// timeout reached
}
}
}
contexts := make([]*context.Context, len(elements))
for _, element:= range elements{
wg.Add(1)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
contexts = append(contexts, ctx)
go doWork(ctx, element, &wg)
}
wg.Wait()
for i, ctx := range contexts {
if ctx.Err() {
fmt.Println("Go routine ", i, "canceled due to", ctx.Err())
}
}
I indeed had the same question and came up with this approach:
https://play.golang.org/p/9F9T_sYIof
Using: context.WithTimeout(context.Background(), 10*time.Second)
:
https://play.golang.org/p/WK0ebe0c9t
Don't know if is the proper way of doing it but is working:
package main
import (
"context"
"fmt"
"sync"
"time"
)
func doWork(element int, wg *sync.WaitGroup) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
defer cancel()
ch := make(chan struct{})
go func(ch chan struct{}) {
time.Sleep(time.Second)
fmt.Printf("element = %+v
", element)
ch <- struct{}{}
}(ch)
select {
case <-ch:
case <-ctx.Done():
fmt.Println(ctx.Err())
}
wg.Done()
}
func main() {
var wg sync.WaitGroup
elements := []int{1, 2, 3}
for _, element := range elements {
wg.Add(1)
go doWork(element, &wg)
}
wg.Wait()
}
Notice the goroutine within the doWork
function:
go func(ch chan struct{}) {
// your code logic goes here
}(ch)
That's the part that I don't know if is the best way of doing it, but seems to be the pattern to follow when using context mainly when want to deal with the ctx.Done()