Go

Spot the goroutine leak.

Question 241MediumGo 1.22 to 1.25
func fastest(ctx context.Context, mirrors []string) string {
	ch := make(chan string)
	for _, m := range mirrors {
		go func() { ch <- fetch(ctx, m) }()
	}
	return <-ch
}

Only the first result is received. The other len(mirrors)-1 goroutines block forever on the unbuffered send, and each one pins its stack and the response memory. Called once per request, this steadily leaks memory and goroutines.

Fixes:

func fastest(ctx context.Context, mirrors []string) string {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel() // tell the losers to stop working
	ch := make(chan string, len(mirrors)) // every sender can complete
	for _, m := range mirrors {
		go func() { ch <- fetch(ctx, m) }()
	}
	return <-ch
}
  • A buffer of len(mirrors) guarantees that no sender ever blocks.
  • Cancelling the context makes the losing fetches stop early instead of finishing work nobody will read.
  • An alternative is a select with default in the sender, dropping the result when nobody is listening.

The general rule: for every goroutine you start, know exactly how it will exit.

More on Concurrency Patterns & sync

All 38 Concurrency Patterns & sync questions