4

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

3 Answers 3

1

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())
  }
}
Sign up to request clarification or add additional context in comments.

Comments

1

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

}

2 Comments

The context.WithDeadline call can be simplified to context.WithTimeout(context.Background(), 100*time.Millisecond)
@Peter: WithTimeout itself calls WithDeadline see inside: func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) }
1

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() {
        time.Sleep(time.Second)
        fmt.Printf("element = %+v\n", element)
        ch <- struct{}{}
    }()

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

2 Comments

The context.WithDeadline call can be simplified to context.WithTimeout(context.Background(), 10*time.Second).
this code will create memory leak as this is unbuffered channel ch := make(chan struct{}) so change to ch := make(chan struct{}, 1) to avoid memory leaks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.