Why does my worker pool hang on shutdown even after I cancel the context Concurrency
Eight workers pull jobs off a buffered channel (cap 100) and push results onto an unbuffered results channel. On SIGINT I call cancel() then wg.Wait(), and roughly one run in three it just sits there forever instead of exiting. Each worker does a select on ctx.Done() and the jobs channel, so I assumed cancellation was covered. Adding a counter shows two workers never return. What am I missing?
@parquet_pile · 7mo ago · 3 replies
Your workers aren't stuck on the select, they're stuck on the send. Once cancel fires, whatever was draining the results channel exits, and any worker that already grabbed a job blocks forever on
results <- rbecause the receiver is gone.ctx.Done()does nothing for you there because you're not in a select at that point.Two fixes, pick one:
select { case results <- r: case <-ctx.Done(): return }The second is usually cleaner because it means results actually get flushed instead of silently dropped on shutdown.
Reply
Report
@oncall_omar · 7mo ago
That's exactly it. My consumer returns on
<-ctx.Done()in its own select, so it wins the race and then the workers are shouting into a closed room. Wrapping the send fixed it in 200 consecutive runs.Reply
Report
@blue_team_bex · 7mo ago
Worth adding the rule that saved me a lot of these: the goroutine that sends on a channel owns closing it, and the goroutine that receives never returns early unless it's also the one that told the senders to stop. Break either half and you get exactly this hang.
Reply
Report