diff --git a/cmd/synapse-backupper/run.go b/cmd/synapse-backupper/run.go index 87c6239..95cacc4 100644 --- a/cmd/synapse-backupper/run.go +++ b/cmd/synapse-backupper/run.go @@ -29,7 +29,7 @@ var ( ) { return cron.NewCronScheduler(expr, job) } - newHealthz = func(port int) (*healthz.Server, error) { + newHealthz = func(port int) (healthz.Server, error) { return healthz.New(port) } runOnceFunc = backup.RunOnce diff --git a/pkg/adapters/healthz/healthz.go b/pkg/adapters/healthz/healthz.go index 19d529a..73667bc 100644 --- a/pkg/adapters/healthz/healthz.go +++ b/pkg/adapters/healthz/healthz.go @@ -9,23 +9,34 @@ import ( "time" ) -// Server is a minimal HTTP health check server. -type Server struct { +// Server is the public interface of the minimal HTTP health check server. +type Server interface { + // Addr returns the bound network address (e.g. "127.0.0.1:8080"). + Addr() string + // Start begins serving HTTP requests. It blocks until Stop is called. + Start() error + // Stop initiates graceful shutdown. After Stop is called the /healthz + // endpoint returns 503 while in-flight requests complete. + Stop(ctx context.Context) error +} + +// server is the private implementation of Server. +type server struct { listener net.Listener - server *http.Server + httpServer *http.Server shuttingDown atomic.Bool } // New creates a health check server listening on the given port. // Passing port 0 binds to an available ephemeral port. -func New(port int) (*Server, error) { +func New(port int) (Server, error) { listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) if err != nil { return nil, fmt.Errorf("failed to create listener: %w", err) } - s := &Server{listener: listener} - s.server = &http.Server{ + s := &server{listener: listener} + s.httpServer = &http.Server{ Handler: http.HandlerFunc(s.handleHealthz), ReadHeaderTimeout: 5 * time.Second, } @@ -33,7 +44,7 @@ func New(port int) (*Server, error) { } // Addr returns the bound network address (e.g. "127.0.0.1:8080"). -func (s *Server) Addr() string { +func (s *server) Addr() string { if s.listener == nil { return "" } @@ -41,21 +52,21 @@ func (s *Server) Addr() string { } // Start begins serving HTTP requests. It blocks until Stop is called. -func (s *Server) Start() error { - return s.server.Serve(s.listener) +func (s *server) Start() error { + return s.httpServer.Serve(s.listener) } // Stop initiates graceful shutdown. After Stop is called the /healthz // endpoint returns 503 while in-flight requests complete. -func (s *Server) Stop(ctx context.Context) error { +func (s *server) Stop(ctx context.Context) error { s.shuttingDown.Store(true) // Grace period so that health checks can observe the 503 state // before the listener is closed. time.Sleep(100 * time.Millisecond) - return s.server.Shutdown(ctx) + return s.httpServer.Shutdown(ctx) } -func (s *Server) handleHealthz(w http.ResponseWriter, r *http.Request) { +func (s *server) handleHealthz(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/healthz" { http.NotFound(w, r) return