Middle+
func fetch(ctx context.Context, u User) (string, error) { // do something over the network time.Sleep(time.Millisecond * 10) // simulate delay return u.Name, nil //return "", errors.New("some error") } func Do(ctx context.Context, users []User) (map[string]int64, error) { var mu sync.Mutex var wg sync.WaitGroup ch := make(chan error, len(users)) names := make(map[string]int64, 0) ctxWithCancel, cancel := context.WithCancel(ctx) wg.Add(len(users)) for _, u := range users { go func() { defer wg.Done() select { case name, err := fetch(ctx, u): if err != nil { ch <- err return } mu.Lock() names[name] = names[name] + 1 mu.Unlock() case <-ctxWithCancel.Err: } }() }
sobes.tech AI
Answer from AI
The provided code has several issues:
-
A goroutine is launched inside a loop, but the variable
uis not passed into the closure, which will lead to data races and incorrect results. -
The
selectstatement usescase name, err := fetch(ctx, u):, butselectdoes not work with function calls; it expects channels. -
There is no call to
cancel()upon an error to stop other goroutines. -
The error channel
chis not used to return errors from theDofunction.
Correct approach:
- Pass
uas a parameter to the anonymous function. - Call
fetchinside the goroutine, and send the result to a channel. - Use
context.WithCancelto cancel all operations upon an error. - Collect results after all goroutines have finished.
Example of corrected code:
func Do(ctx context.Context, users []User) (map[string]int64, error) {
var mu sync.Mutex
names := make(map[string]int64)
ctxWithCancel, cancel := context.WithCancel(ctx)
defer cancel()
errCh := make(chan error, len(users))
wg := sync.WaitGroup{}
wg.Add(len(users))
for _, u := range users {
u := u // capture variable
go func() {
defer wg.Done()
name, err := fetch(ctxWithCancel, u)
if err != nil {
errCh <- err
cancel()
return
}
mu.Lock()
names[name]++
mu.Unlock()
}()
}
wg.Wait()
close(errCh)
for err := range errCh {
if err != nil {
return nil, err
}
}
return names, nil
}
This approach correctly handles errors, synchronizes access to the map, and cancels operations if necessary.