Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang WaitGroup Timeout Error handling

Tags:

go

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

like image 418
Moonlit Avatar asked Sep 06 '25 03:09

Moonlit


2 Answers

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())
  }
}
like image 107
Artem Barger Avatar answered Sep 07 '25 22:09

Artem Barger


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())
    }

}
like image 39
wasmup Avatar answered Sep 07 '25 23:09

wasmup