Go

Implement graceful shutdown for an HTTP server with background workers.

Question 248HardGo 1.22 to 1.25
func main() {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	var workers sync.WaitGroup
	jobs := make(chan Job, 100)
	for range 4 {
		workers.Go(func() {
			for j := range jobs { // drains until jobs is closed
				process(j)
			}
		})
	}

	// NOT the signal ctx: that would cancel in-flight requests the moment
	// SIGTERM arrives, defeating the drain.
	srv := &http.Server{
		Addr:    ":8080",
		Handler: newRouter(jobs),
	}

	errCh := make(chan error, 1)
	go func() { errCh <- srv.ListenAndServe() }()

	select {
	case err := <-errCh:
		log.Fatalf("listen: %v", err)
	case <-ctx.Done():
		stop() // a second Ctrl-C now kills immediately
	}

	shutCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()
	if err := srv.Shutdown(shutCtx); err != nil { // stop accepting, drain in-flight
		log.Printf("shutdown: %v", err)
	}
	close(jobs)    // safe only if Shutdown returned nil (all handlers done)
	workers.Wait() // finish queued work
	log.Println("bye")
}

Ordering matters:

  1. Stop taking new work: Shutdown closes the listeners and waits for active requests.
  2. Close the producer side (jobs).
  3. Drain the workers.
  4. Flush and close resources such as DB connections and tracers.

Gotchas:

  • After Shutdown is called, ListenAndServe returns http.ErrServerClosed immediately.
  • Shutdown does not wait for hijacked or WebSocket connections. Use RegisterOnShutdown for those.
  • In Kubernetes, fail readiness first and sleep briefly so the endpoint is removed before you stop accepting.
  • Always put a deadline on shutdown. If Shutdown times out, some handlers may still be running, and one that enqueues onto the closed jobs channel panics. Either have handlers check a "shutting down" flag or context before sending, or exit without closing jobs on timeout.
  • Do not set BaseContext to the signal context. Request contexts would be cancelled on SIGTERM instead of being allowed to finish.

More on Concurrency Patterns & sync

All 38 Concurrency Patterns & sync questions