Commit 4befdbea authored by Doug Barrett's avatar Doug Barrett
Browse files

fix(v2/httpserver): Fix race condition in router init and use configured logger

Use sync.Once for thread-safe lazy initialization of the handler chain
in defaultRouter.ServeHTTP, preventing concurrent goroutines from
building the chain simultaneously. Also use the server's configured
logger in writeJSON instead of slog.Default() for consistency.
parent 8650e619
Loading
Loading
Loading
Loading
+8 −4
Original line number Diff line number Diff line
@@ -52,7 +52,7 @@ type healthResponse struct {
// livenessHandler always returns 200 OK. It confirms the process is running;
// it does not test any dependencies.
func (s *Server) livenessHandler(w http.ResponseWriter, _ *http.Request) {
	writeJSON(w, http.StatusOK, healthResponse{Status: "ok"})
	s.writeJSON(w, http.StatusOK, healthResponse{Status: "ok"})
}

// checkResult holds the outcome of a single readiness check.
@@ -95,13 +95,17 @@ func (s *Server) readinessHandler(w http.ResponseWriter, r *http.Request) {
	if resp.Status == "error" {
		status = http.StatusServiceUnavailable
	}
	writeJSON(w, status, resp)
	s.writeJSON(w, status, resp)
}

func writeJSON(w http.ResponseWriter, status int, v any) {
func (s *Server) writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	if err := json.NewEncoder(w).Encode(v); err != nil {
		slog.Default().Error("failed to encode health response", "error", err)
		logger := slog.Default()
		if s.cfg.Logger != nil {
			logger = s.cfg.Logger
		}
		logger.Error("failed to encode health response", "error", err)
	}
}
+7 −3
Original line number Diff line number Diff line
package httpserver

import "net/http"
import (
	"net/http"
	"sync"
)

// Router is the interface that the httpserver package uses for route and
// middleware registration. The default implementation wraps Go 1.22+'s
@@ -32,6 +35,7 @@ type defaultRouter struct {
	mux        *http.ServeMux
	middleware []func(http.Handler) http.Handler
	handler    http.Handler // cached chain, built lazily on first ServeHTTP
	once       sync.Once
}

// newDefaultRouter returns a Router that uses [net/http.ServeMux] internally.
@@ -58,9 +62,9 @@ func (r *defaultRouter) Use(middleware ...func(http.Handler) http.Handler) {
// requests. All routes and middleware must therefore be registered before the
// first request is served.
func (r *defaultRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if r.handler == nil {
	r.once.Do(func() {
		r.handler = r.buildHandler()
	}
	})
	r.handler.ServeHTTP(w, req)
}