go - Cancel goroutines on first error using errgroup
问题描述
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)
推荐阅读
- c++ - 在 base 10 c++ 中显示 1024 位二进制数
- python - Pandas:删除具有少于 2 个非零值的项目
- javascript - Puppeteer - 将 elementHandles 传递给模板文字的方法?
- wordpress - 如何使用 $wpdb->get_results() 从数据库中获取选定的列;
- spring-boot - Spring Boot 应用程序失败,Jackson2ObjectMapperBuilder.visibility 不存在
- python-3.x - Matplotlib 文本艺术家 - 如何获得大小?(不使用 pyplot)
- google-cloud-build - Google Cloud Build:如何增加 RAM 内存?
- ms-access - 在具有多列的访问数据库中排序
- php - 如何在 Woocommerce 中以编程方式安排销售日期
- django - 如何解决 IntegrityError: NOT NULL 约束失败?