Senior · Gin services
Graceful shutdown with workers and WebSockets
The server calls http.Server.Shutdown and exits as soon as it returns. The application also has workers and hijacked connections. Is shutdown complete?
Try explaining this aloud before opening the answer. State your assumptions and trace a boundary case.
package main
import (
"context"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func stop(srv *http.Server) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return srv.Shutdown(ctx)
}
func main() {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/ready", func(c *gin.Context) { c.Status(200) })
srv := &http.Server{Addr: "127.0.0.1:8080", Handler: r, ReadHeaderTimeout: 2 * time.Second, IdleTimeout: 60 * time.Second, MaxHeaderBytes: 16 << 10}
// Configuration example: no listener is opened by this program.
fmt.Println(srv.ReadHeaderTimeout, srv.IdleTimeout, srv.MaxHeaderBytes)
}Reveal a strong answer
A router handles requests, while net/http.Server owns listener behavior and connection budgets. A short r.Run example is useful for a tutorial, but a deployed service needs an explicit startup and shutdown owner. Readiness, request admission, long-lived streams, background workers and shared clients must fit the same lifecycle rather than each inventing its own stop signal.
Reject new work during drain and stop reporting readiness before termination when the platform requires it. Allow in-flight operations a defined budget, then join owned workers and close shared dependencies in order. ReadTimeout and WriteTimeout interact with request bodies and streaming, so configure them for the protocol instead of copying one magic timeout to every service. The constructor makes header and idle budgets visible. stop creates a fresh bounded shutdown context rather than using an already-cancelled request context. This example opens no socket. In a native service, separately own Serve or ListenAndServe, distinguish http.ErrServerClosed from a startup failure and decide how to handle shutdown exceeding its budget.
Shutdown does not close or wait for hijacked connections such as custom WebSocket lifetimes. A readiness endpoint that always returns 200 does not prove downstream capacity; avoid making liveness fail merely because a dependency is temporarily unavailable. Test a slow header, cancellation, a long request during drain and a worker that ignores its stop contract in a native integration environment.
Common trap
A returned Shutdown call is not proof that every resource and background effect is finished.
Try it
Design readiness withdrawal, request drain, worker joining, stream handling and dependency-close order within one termination budget.
Worked solution
Server.Shutdown drains its managed HTTP connections under a context, but does not own every goroutine or wait for hijacked connections. The composition root must separately stop and join workers, manage long-lived connection lifetimes and close shared clients after their borrowers finish. Timeouts depend on the protocol, especially streaming; verify cancellation and test a slow operation during drain.
Change the constraint
How do Kubernetes termination grace and load-balancer propagation affect the available application budget?
How did you do?
Self-assessment, not a compiler pass or interview score. Explain the reasoning unaided before marking understood.
Not marked. Progress stays on this browser, separate from course completion.
Primary references
Checked 2026-10-28. Use documentation for your pinned runtime, driver and tool version.