feat(process): add readiness polling helpers
This commit is contained in:
parent
1398c4b8ea
commit
ac5a938b70
2 changed files with 92 additions and 4 deletions
69
health.go
69
health.go
|
|
@ -118,11 +118,14 @@ func (h *HealthServer) Start() error {
|
|||
return coreerr.E("HealthServer.Start", fmt.Sprintf("failed to listen on %s", h.addr), err)
|
||||
}
|
||||
|
||||
server := &http.Server{Handler: mux}
|
||||
h.mu.Lock()
|
||||
h.listener = listener
|
||||
h.server = &http.Server{Handler: mux}
|
||||
h.server = server
|
||||
h.mu.Unlock()
|
||||
|
||||
go func() {
|
||||
_ = h.server.Serve(listener)
|
||||
_ = server.Serve(listener)
|
||||
}()
|
||||
|
||||
return nil
|
||||
|
|
@ -134,10 +137,17 @@ func (h *HealthServer) Start() error {
|
|||
//
|
||||
// _ = server.Stop(context.Background())
|
||||
func (h *HealthServer) Stop(ctx context.Context) error {
|
||||
if h.server == nil {
|
||||
h.mu.Lock()
|
||||
server := h.server
|
||||
h.server = nil
|
||||
h.listener = nil
|
||||
h.ready = false
|
||||
h.mu.Unlock()
|
||||
|
||||
if server == nil {
|
||||
return nil
|
||||
}
|
||||
return h.server.Shutdown(ctx)
|
||||
return server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// Addr returns the actual address the server is listening on.
|
||||
|
|
@ -146,6 +156,8 @@ func (h *HealthServer) Stop(ctx context.Context) error {
|
|||
//
|
||||
// addr := server.Addr()
|
||||
func (h *HealthServer) Addr() string {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.listener != nil {
|
||||
return h.listener.Addr().String()
|
||||
}
|
||||
|
|
@ -200,3 +212,52 @@ func ProbeHealth(addr string, timeoutMs int) (bool, string) {
|
|||
}
|
||||
return false, lastReason
|
||||
}
|
||||
|
||||
// WaitForReady polls `/ready` until it responds 200 or the timeout expires.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// if !process.WaitForReady("127.0.0.1:8080", 5_000) {
|
||||
// return errors.New("service did not become ready")
|
||||
// }
|
||||
func WaitForReady(addr string, timeoutMs int) bool {
|
||||
ok, _ := ProbeReady(addr, timeoutMs)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ProbeReady polls `/ready` until it responds 200 or the timeout expires.
|
||||
// It returns the readiness status and the last observed failure reason.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// ok, reason := process.ProbeReady("127.0.0.1:8080", 5_000)
|
||||
func ProbeReady(addr string, timeoutMs int) (bool, string) {
|
||||
deadline := time.Now().Add(time.Duration(timeoutMs) * time.Millisecond)
|
||||
url := fmt.Sprintf("http://%s/ready", addr)
|
||||
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
var lastReason string
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
resp, err := client.Get(url)
|
||||
if err == nil {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return true, ""
|
||||
}
|
||||
lastReason = strings.TrimSpace(string(body))
|
||||
if lastReason == "" {
|
||||
lastReason = resp.Status
|
||||
}
|
||||
} else {
|
||||
lastReason = err.Error()
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
if lastReason == "" {
|
||||
lastReason = "readiness check timed out"
|
||||
}
|
||||
return false, lastReason
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,3 +90,30 @@ func TestWaitForHealth_Unreachable(t *testing.T) {
|
|||
ok := WaitForHealth("127.0.0.1:19999", 500)
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestWaitForReady_Reachable(t *testing.T) {
|
||||
hs := NewHealthServer("127.0.0.1:0")
|
||||
require.NoError(t, hs.Start())
|
||||
defer func() { _ = hs.Stop(context.Background()) }()
|
||||
|
||||
ok := WaitForReady(hs.Addr(), 2_000)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestWaitForReady_Unreachable(t *testing.T) {
|
||||
ok := WaitForReady("127.0.0.1:19999", 500)
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestHealthServer_StopMarksNotReady(t *testing.T) {
|
||||
hs := NewHealthServer("127.0.0.1:0")
|
||||
require.NoError(t, hs.Start())
|
||||
|
||||
require.NotEmpty(t, hs.Addr())
|
||||
assert.True(t, hs.Ready())
|
||||
|
||||
require.NoError(t, hs.Stop(context.Background()))
|
||||
|
||||
assert.False(t, hs.Ready())
|
||||
assert.NotEmpty(t, hs.Addr())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue