How do you gracefully shut down a service that uses channels and goroutines?
Question 216HardGo 1.22 to 1.25
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
jobs := make(chan Job, 100)
var wg sync.WaitGroup
for range 4 {
wg.Add(1)
go func() {
defer wg.Done()
for j := range jobs { // drains the remaining jobs, then exits
j.Run()
}
}()
}
go produce(ctx, jobs) // stops producing when ctx is done, then close(jobs)
<-ctx.Done()
log.Println("shutting down")
done := make(chan struct{})
go func() { wg.Wait(); close(done) }()
select {
case <-done:
log.Println("clean exit")
case <-time.After(10 * time.Second):
log.Println("forced exit; workers still busy")
}
}
The sequence: (1) catch the signal and turn it into a context; (2) stop the intake (the producer stops and closes jobs); (3) let workers drain the in-flight work via range; (4) wait with a hard deadline, using the WaitGroup-to-channel trick so the wait can go in a select. The interviewer is looking for: shutdown flows from producers to consumers, a single closer, and a bounded wait. For HTTP, also call srv.Shutdown(ctx).
More on Channels & select
- Q214How would you implement rate limiting using channels?
- Q215Can you compare channels with
==, use them as map keys, and send channels over channels? - Q217What does this print? (
breakinsideselectinsidefor) - Q218What happens to goroutines that are already blocked on a channel when it is closed?
- Q219Go has no unbounded channels. Why not, and how would you build one?
- Q220What exactly gets copied when you send on a channel, and what limits apply to element types and buffer sizes?