Commit 8650e619 authored by Doug Barrett's avatar Doug Barrett
Browse files

fix(v2/httpserver): Run readiness checks concurrently and log JSON encoding errors

Execute readiness checks using goroutines so a slow check does not block
others, matching the documented concurrent behavior. Also log JSON
encoding failures in writeJSON for debugging.
parent 1e1d490a
Loading
Loading
Loading
Loading
+30 −7
Original line number Diff line number Diff line
@@ -3,7 +3,9 @@ package httpserver
import (
	"context"
	"encoding/json"
	"log/slog"
	"net/http"
	"sync"
)

// CheckFunc is a health check function. It should return nil when the
@@ -53,19 +55,38 @@ func (s *Server) livenessHandler(w http.ResponseWriter, _ *http.Request) {
	writeJSON(w, http.StatusOK, healthResponse{Status: "ok"})
}

// readinessHandler runs every registered CheckFunc and returns 200 when all
// pass or 503 when one or more fail, with per-check detail in the JSON body.
// checkResult holds the outcome of a single readiness check.
type checkResult struct {
	name string
	err  error
}

// readinessHandler runs every registered CheckFunc concurrently and returns
// 200 when all pass or 503 when one or more fail, with per-check detail in
// the JSON body.
func (s *Server) readinessHandler(w http.ResponseWriter, r *http.Request) {
	resp := healthResponse{Status: "ok"}

	if len(s.checks) > 0 {
		results := make([]checkResult, len(s.checks))
		var wg sync.WaitGroup
		wg.Add(len(s.checks))

		for i, c := range s.checks {
			go func(idx int, check readinessCheck) {
				defer wg.Done()
				results[idx] = checkResult{name: check.name, err: check.fn(r.Context())}
			}(i, c)
		}
		wg.Wait()

		resp.Checks = make(map[string]string, len(s.checks))
		for _, c := range s.checks {
			if err := c.fn(r.Context()); err != nil {
		for _, res := range results {
			if res.err != nil {
				resp.Status = "error"
				resp.Checks[c.name] = err.Error()
				resp.Checks[res.name] = res.err.Error()
			} else {
				resp.Checks[c.name] = "ok"
				resp.Checks[res.name] = "ok"
			}
		}
	}
@@ -80,5 +101,7 @@ func (s *Server) readinessHandler(w http.ResponseWriter, r *http.Request) {
func writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(v)
	if err := json.NewEncoder(w).Encode(v); err != nil {
		slog.Default().Error("failed to encode health response", "error", err)
	}
}