From 298a7d69a7f744ade77bd5467a952331e9d16e1a Mon Sep 17 00:00:00 2001 From: Jared Jakacky Date: Tue, 19 May 2026 11:47:04 -0500 Subject: [PATCH] fix(loop): preserve domain-gated readiness --- loop.go | 7 ++++++- loop_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/loop.go b/loop.go index 0709de9..7d43e56 100644 --- a/loop.go +++ b/loop.go @@ -167,6 +167,11 @@ func (w *LoopWorker) Start(ctx context.Context) error { return err } } + if !w.autoReady { + if err := runtime.SetReady(false); err != nil { + return err + } + } // Preserve Start context values for telemetry/correlation, but detach from // Start cancellation because the loop outlives the Start call. Stop uses this @@ -179,7 +184,7 @@ func (w *LoopWorker) Start(ctx context.Context) error { go w.runLoop(loopCtx, runtime, done) if !w.autoReady { - return runtime.SetReady(false) + return nil } if err := runtime.SetReady(true); err != nil { w.markStopping(done) diff --git a/loop_test.go b/loop_test.go index 5869ff1..c49df24 100644 --- a/loop_test.go +++ b/loop_test.go @@ -9,6 +9,18 @@ import ( "time" ) +type loopReadyFailRuntime struct { + fakeWorkerRuntime + readyFailure error +} + +func (r loopReadyFailRuntime) SetReady(ready bool) error { + if !ready { + return r.readyFailure + } + return nil +} + func TestLoopWorkerStartRejectsNilLoop(t *testing.T) { worker := NewLoopWorker(nil) err := worker.Start(withWorkerRuntime(context.Background(), fakeWorkerRuntime{})) @@ -187,6 +199,35 @@ func TestLoopWorkerLoopCanMarkReadyWhenAutoReadyDisabled(t *testing.T) { } } +func TestLoopWorkerAutoReadyDisabledSetsNotReadyBeforeLoop(t *testing.T) { + readyFailure := errors.New("ready false failed") + worker := NewLoopWorker( + func(ctx context.Context, _ WorkerRuntime) error { + <-ctx.Done() + return ctx.Err() + }, + WithLoopAutoReady(false), + ) + + err := worker.Start(withWorkerRuntime(context.Background(), loopReadyFailRuntime{ + readyFailure: readyFailure, + })) + if !errors.Is(err, readyFailure) { + t.Fatalf("Start error = %v, want %v", err, readyFailure) + } + + worker.mu.Lock() + state := worker.state + done := worker.done + worker.mu.Unlock() + if state != loopIdle { + t.Fatalf("loop worker state = %s, want %s", state, loopIdle) + } + if done != nil { + t.Fatal("loop worker done channel is set, want nil") + } +} + func TestLoopWorkerStopCancelsLoopAndRunsStopHookOnce(t *testing.T) { loopDone := make(chan struct{}) var stopCalls atomic.Int32