Includes solved 02-worker-pool and 09-safe-counter (mutex, atomic, sharded). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GbnJj9ZqEx9nUvzkjdwfMq
70 lines
2.3 KiB
Go
70 lines
2.3 KiB
Go
// 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")
|
|
}
|