check / check (push) Successful in 1m11s
The server ran os.Exit at the end of its own goroutine, racing fx's teardown and sometimes killing the process before reportbuf's OnStop flushed — silently losing a full flush window of telemetry on every restart, at exit 0. Shutdown now goes through fx.Shutdowner, so every OnStop runs in order. The http.Server is built synchronously in OnStart before the serving goroutine, so shutdown can no longer race or nil-deref it. A listen failure exits non-zero via fx.ExitCode(1). reportbuf's OnStop is guarded by sync.Once. writeTimeout now exceeds the chi per-request budget so that budget is reachable. Dead startupTime, exitCode, and cancelFunc fields are gone. A new test asserts a buffered report reaches disk after the lifecycle stops. Model: opus-4-8
67 lines
1.8 KiB
Go
67 lines
1.8 KiB
Go
package server
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
const (
|
|
readTimeout = 10 * time.Second
|
|
readHeaderTimeout = 5 * time.Second
|
|
idleTimeout = 60 * time.Second
|
|
maxHeaderBytes = 1 << 20 // 1 MiB
|
|
|
|
// requestTimeout (routes.go) is the single per-request
|
|
// processing budget, enforced by chi's middleware.Timeout.
|
|
// writeTimeout must exceed that budget so a handler can write
|
|
// its 503 when the chi timeout fires; if it were shorter the
|
|
// server would abort the write first and the chi budget would
|
|
// be unreachable dead configuration.
|
|
writeTimeout = requestTimeout + 5*time.Second
|
|
)
|
|
|
|
// newHTTPServer constructs the http.Server. It performs no I/O
|
|
// and does not start listening.
|
|
func (s *Server) newHTTPServer() *http.Server {
|
|
listenAddr := fmt.Sprintf(":%d", s.params.Config.Port)
|
|
|
|
return &http.Server{
|
|
Addr: listenAddr,
|
|
Handler: s,
|
|
MaxHeaderBytes: maxHeaderBytes,
|
|
ReadTimeout: readTimeout,
|
|
ReadHeaderTimeout: readHeaderTimeout,
|
|
WriteTimeout: writeTimeout,
|
|
IdleTimeout: idleTimeout,
|
|
}
|
|
}
|
|
|
|
// listenAndServe runs the listener until the server is shut
|
|
// down. A genuine listen failure (not the expected
|
|
// ErrServerClosed from a clean shutdown) requests process
|
|
// shutdown through fx with a non-zero exit code, so the failure
|
|
// is visible to any supervisor.
|
|
func (s *Server) listenAndServe() {
|
|
s.log.Info("http begin listen",
|
|
"listenaddr", s.httpServer.Addr,
|
|
"version", s.params.Globals.Version,
|
|
"buildarch", s.params.Globals.Buildarch,
|
|
)
|
|
|
|
err := s.httpServer.ListenAndServe()
|
|
if err == nil || errors.Is(err, http.ErrServerClosed) {
|
|
return
|
|
}
|
|
|
|
s.log.Error("listen error", "error", err)
|
|
|
|
shutdownErr := s.shutdowner.Shutdown(fx.ExitCode(1))
|
|
if shutdownErr != nil {
|
|
s.log.Error("request shutdown failed", "error", shutdownErr)
|
|
}
|
|
}
|