Sobes.tech
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:

  1. A goroutine is launched inside a loop, but the variable u is not passed into the closure, which will lead to data races and incorrect results.

  2. The select statement uses case name, err := fetch(ctx, u):, but select does not work with function calls; it expects channels.

  3. There is no call to cancel() upon an error to stop other goroutines.

  4. The error channel ch is not used to return errors from the Do function.

Correct approach:

  • Pass u as a parameter to the anonymous function.
  • Call fetch inside the goroutine, and send the result to a channel.
  • Use context.WithCancel to 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.

func fetch(ctx context.Context, u User) (string… - sobes.tech