Return the fastest of N searchers run concurrently
Implement getFastestSearcher: run testSearcher(ctx, name) concurrently for every name in searches and return the name whose call reported the smallest time.Duration, together with that duration. Requirements: run the probes concurrently, not one at a time; propagate ctx so cancellation stops in-flight probes; if every probe fails, return the last error; do not leak goroutines.
func getFastestSearcher(ctx context.Context, searches []string) (name string, respTime time.Duration, err error) {
// your code here
}
Write the implementation.
Launch one goroutine per name, each timing testSearcher(ctx, name) and sending a {name, dur, err} result to a buffered channel that a closer goroutine closes after a WaitGroup. Range the channel, keep the result with the smallest dur among successes, and propagate ctx so cancellation stops in-flight probes. If all fail, return the last error.
- ✗Returning the first searcher to respond instead of the one with the smallest reported duration
- ✗Updating shared
name/respTimefrom goroutines without synchronization — a data race - ✗Closing the results channel from a sender, or never closing it, so the
rangeblocks forever
- →How would you return early once a searcher beats a target latency, cancelling the rest?
- →Why must the channel be buffered (or sends guarded) to avoid leaking goroutines on early return?
Skeleton to implement
Buffer the results channel to the number of probes so senders never block, and close it from a separate goroutine after wg.Wait. Range the channel, keeping the probe with the smallest dur among successes; if none succeed, return the last error. ctx is passed into every testSearcher, and the send is guarded by a select on ctx.Done() so no goroutine leaks on an early return.
type res struct {
name string
dur time.Duration
err error
}
func getFastestSearcher(ctx context.Context, searches []string) (name string, respTime time.Duration, err error) {
out := make(chan res, len(searches)) // buffer sized to probes — senders never block
var wg sync.WaitGroup
for _, s := range searches {
wg.Add(1)
go func(s string) {
defer wg.Done()
d, e := testSearcher(ctx, s)
select {
case out <- res{s, d, e}:
case <-ctx.Done(): // do not block if we leave early
}
}(s)
}
go func() { wg.Wait(); close(out) }() // close only after every Done
var best res
found := false
for r := range out {
if r.err != nil {
err = r.err // remember the last error
continue
}
if !found || r.dur < best.dur {
best, found = r, true
}
}
if found {
return best.name, best.dur, nil
}
return "", 0, err
}