From 31b31c7635c711ea0fa4f37da70b819a976c72b7 Mon Sep 17 00:00:00 2001 From: thuanle Date: Tue, 28 Apr 2026 00:25:47 +0700 Subject: [PATCH] fix: graceful HTTP server shutdown on SIGINT/SIGTERM Replace router.Run() with explicit http.Server + Shutdown() so the process exits cleanly when context is cancelled. Co-Authored-By: Claude Opus 4.7 --- cmd/bridge/main.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cmd/bridge/main.go b/cmd/bridge/main.go index 6e661e1..5a6fa03 100644 --- a/cmd/bridge/main.go +++ b/cmd/bridge/main.go @@ -3,10 +3,12 @@ package main import ( "context" "log/slog" + "net/http" "os" "os/signal" "sync" "syscall" + "time" "thuanle.me/claw-email-bridge/internal/ai_client" "thuanle.me/claw-email-bridge/internal/api" @@ -62,16 +64,30 @@ func main() { // Start HTTP server with SMTP egress on callback. smtpSender := mail.NewSMTPSender(cfg, db) router := api.NewRouter(db, cfg.BridgeCallbackToken, smtpSender.SendReplyFunc()) + srv := &http.Server{Addr: cfg.ListenAddr, Handler: router} + wg.Add(1) go func() { defer wg.Done() slog.Info("starting HTTP server", "addr", cfg.ListenAddr) - if err := router.Run(cfg.ListenAddr); err != nil { + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { slog.Error("server exited with error", "error", err) cancel() } }() + // Graceful shutdown: wait for context cancel then shutdown HTTP server. + wg.Add(1) + go func() { + defer wg.Done() + <-ctx.Done() + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer shutdownCancel() + if err := srv.Shutdown(shutdownCtx); err != nil { + slog.Error("server shutdown error", "error", err) + } + }() + wg.Wait() slog.Info("bridge stopped") }