I'm trying to cancel remaining goroutines after an error being encountered in one of them or at least cancel the fetch function call in them.
func fetch(n int, fail bool) (string, error) {
time.Sleep(time.Duration(n) * time.Second)
if fail {
return "", errors.New("an error") //fmt.Errorf("an error")
} else {
return "Hello", nil
}
}
func getData(ctx context.Context, ch chan string, n int, fail bool) error {
fmt.Println("fetch data" + strconv.Itoa(n))
select {
case <-ctx.Done():
return ctx.Err()
default:
if response, err := fetch(n, fail); err != nil {
fmt.Println("error encountered at ", strconv.Itoa(n))
return err
} else {
ch <- response
fmt.Println("fetched data" + strconv.Itoa(n))
return nil
}
}
}
func main() {
res, err := func() (string, error) {
ctx := context.Background()
g, ctx := errgroup.WithContext(ctx)
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
ch3 := make(chan string, 1)
g.Go(func() error {
defer close(ch1)
return getData(ctx, ch1, 1, true)
})
g.Go(func() error {
defer close(ch2)
return getData(ctx, ch2, 2, false)
})
g.Go(func() error {
defer close(ch3)
return getData(ctx, ch3, 3, false)
})
result := <-ch1 + <-ch2 + <-ch3
return result, g.Wait()
}()
if err != nil {
fmt.Println(err)
} else {
fmt.Println(res)
}
}
The output should look like:
fetch data1
fetch data2
fetch data3
error encountered at 1
an error
but actually is:
fetch data3
fetch data1
fetch data2
error encountered at 1
fetched data2
fetched data3
an error
What I understand the errgroup should be doing is closing the context when an error is found, and then <-ctx.Done() should return ctx.Err(), but instead It's like the context is never done and the fetch function keeps being called.
I think the problem is in the select statement. Any idea of what i'm doing wrong or missing? Link to playground
Pass the context to the fetch function and exit on cancel:
func fetch(ctx context.Context, n int, fail bool) (string, error) {
select {
case <-time.After(time.Duration(n) * time.Second):
if fail {
return "", errors.New("an error")
} else {
return "Hello", nil
}
case <-ctx.Done():
fmt.Println("canceled")
return "CXL", ctx.Err()
}
}
There's no need for the channel stuff and GetData. Just assign results from fetch to variables.
ctx := context.Background()
g, ctx := errgroup.WithContext(ctx)
var r1, r2, r3 string
g.Go(func() error {
var err error
r1, err = fetch(ctx, 1, true)
return err
})
g.Go(func() error {
var err error
r2, err = fetch(ctx, 2, false)
return err
})
g.Go(func() error {
var err error
r3, err = fetch(ctx, 3, false)
return err
})
err := g.Wait()
fmt.Printf("err: %v, 1: %s; 2: %s; 3: %s\n", err, r1, r2, r3)
Run it on the playground.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With