// Exercise 08 — Graceful HTTP Server Shutdown // // ASSIGNMENT // ---------- // Implement NewServer and Run: // // 1. NewServer(addr, handler) returns an *http.Server with EXPLICIT // ReadTimeout, WriteTimeout and IdleTimeout (pick sane values; the // zero-value server has none, which is the bug). // 2. Run(ctx, srv, drainTimeout): // a. Starts the server (ListenAndServe) in a goroutine. // b. Blocks until ctx is cancelled (that's your SIGTERM signal). // c. Then calls srv.Shutdown with a FRESH context carrying // drainTimeout — NOT the already-cancelled ctx (why? see README). // d. In-flight requests must be allowed to complete (up to drainTimeout); // new connections are refused during drain. // e. Returns nil on clean drain; returns an error if the drain deadline // was exceeded or ListenAndServe failed for a real reason. // (http.ErrServerClosed is NOT a real reason.) // // Run the tests: go test -race ./08-graceful-shutdown/ // Run the demo: go run ./08-graceful-shutdown/ (then Ctrl+C) package main import ( "context" "fmt" "net/http" "os/signal" "syscall" "time" ) // NewServer builds an http.Server with production timeouts. // TODO: implement me. func NewServer(addr string, h http.Handler) *http.Server { return &http.Server{} } // Run serves until ctx is cancelled, then drains gracefully. // TODO: implement me. func Run(ctx context.Context, srv *http.Server, drainTimeout time.Duration) error { // TODO: // - errCh := make(chan error, 1); go srv.ListenAndServe() into it // (why buffered? so the goroutine never leaks if we exit via ctx) // - select on ctx.Done() vs errCh // - on ctx.Done(): context.WithTimeout(context.Background(), drainTimeout) // and srv.Shutdown(shutCtx) // - filter http.ErrServerClosed with errors.Is return nil } func main() { ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) defer stop() mux := http.NewServeMux() mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) { time.Sleep(3 * time.Second) fmt.Fprintln(w, "done") }) srv := NewServer(":8080", mux) fmt.Println("serving on :8080 — curl localhost:8080/slow, then Ctrl+C mid-request") if err := Run(ctx, srv, 10*time.Second); err != nil { fmt.Println("shutdown error:", err) } fmt.Println("bye") }