Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lifecycle/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ func (lc *Lifecycle) Start(ctx context.Context) (xerr error) {
// Wait for all probes to signal ready
for _, probe := range allProbes {
select {
case <-lc.Done():
return lc.Err()
case <-ctx.Done():
return errors.WithStack(ctx.Err())
case <-probe.Done():
Expand Down
34 changes: 33 additions & 1 deletion lifecycle/lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,39 @@ func TestWithStopPanicBehavior(t *testing.T) {
}, "WithStop should panic when called on Lifecycle")
}

// TestRequiresStopCleanupScenarios tests various scenarios where RequiresStop actors need cleanup
// TestStartDoesNotHangWhenServeFailsDuringProbeWait validates that Start() returns immediately
// when Serve() fails in the background while Start() is waiting for readiness probes.
func TestStartDoesNotHangWhenServeFailsDuringProbeWait(t *testing.T) {
expectedErr := errors.New("actor failed")

lc := New()
defer func() { _ = lc.Stop(context.Background()) }()

// FuncActor that fails after delay - Serve will fail while Start waits for probe
lc.Add(NewFuncActor(func(ctx context.Context) error {
time.Sleep(50 * time.Millisecond)
return expectedErr
}, nil))

// FuncActor configured with WithReadiness. In this test, its Start() is never called because
// the first actor's Serve() fails first, so this actor's readiness probe never signals.
lc.Add(NewFuncActor(nil, nil).WithReadiness())

// Simple FuncService
lc.Add(NewFuncService(func(ctx context.Context) error {
<-ctx.Done()
return nil
}))

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

err := lc.Start(ctx)
require.Error(t, err)
require.NotErrorIs(t, err, context.DeadlineExceeded)
require.Contains(t, err.Error(), "actor failed")
}

func TestRequiresStopCleanupScenarios(t *testing.T) {
t.Run("BuildContext failure should clean up RequiresStop actors", func(t *testing.T) {
var alwaysActiveStopped, normalStopped int32
Expand Down
Loading