diff --git a/backend_health.go b/backend_health.go index 1f6c350..c99cec1 100644 --- a/backend_health.go +++ b/backend_health.go @@ -118,7 +118,7 @@ func (bhm *BackendHealthManager) WaitForBackendReady(timeout time.Duration) erro } // Exponential backoff (1.5x growth, capped at maxDelay), no jitter - time.Sleep(currentDelay) + time.Sleep(min(currentDelay, time.Until(deadline))) currentDelay = time.Duration(float64(currentDelay) * 1.5) if currentDelay > maxDelay { currentDelay = maxDelay diff --git a/backend_health_deadline_test.go b/backend_health_deadline_test.go new file mode 100644 index 0000000..61717f1 --- /dev/null +++ b/backend_health_deadline_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "net" + "testing" + "time" + + libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging" + "github.com/valyala/fasthttp" +) + +// Pins theorems/Theorems/ReadyBackoff.lean: the startup wait never sleeps past its timeout. +func TestWaitForBackendReady_HonoursTimeout(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + addr := ln.Addr().String() + _ = ln.Close() // closed port: every probe is refused immediately + + bhm := NewBackendHealthManager(&fasthttp.Client{}, "http://"+addr, "", libpack_logger.New()) + defer bhm.cancel() + + const timeout = 3 * time.Second + start := time.Now() + if err := bhm.WaitForBackendReady(timeout); err == nil { + t.Fatal("want error for unreachable backend") + } + if elapsed := time.Since(start); elapsed > timeout+500*time.Millisecond { + t.Fatalf("waited %v, want <= %v", elapsed, timeout+500*time.Millisecond) + } +}