Skip to content

Commit 7f2bfa9

Browse files
ValentaTomase2b-bot[bot]
authored andcommitted
fix(orchestrator): finish rejected sandbox lifecycles
GitOrigin-RevId: acee027ae59efd1def5709680fba178199cc9c0c
1 parent a985d95 commit 7f2bfa9

4 files changed

Lines changed: 79 additions & 4 deletions

File tree

‎packages/orchestrator/pkg/sandbox/map_test.go‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,77 @@ func TestMapConcurrentMarkStoppingAndStoppedDoesNotResurrectLifecycle(t *testing
506506
}
507507
}
508508

509+
func TestMapMarkRunningRefusesCollidingLifecycle(t *testing.T) {
510+
t.Parallel()
511+
512+
sandboxes := NewSandboxesMap()
513+
oldSbx := testMapSandbox(t, "lifecycle-old")
514+
newSbx := testMapSandbox(t, "lifecycle-new")
515+
516+
require.NoError(t, sandboxes.MarkRunning(t.Context(), oldSbx))
517+
518+
// The old lifecycle is still live (e.g. it crashed and nothing removed it
519+
// yet): the new lifecycle must be refused, not silently dropped — a silent
520+
// drop leaves its FC process running with no id-based way to reach it.
521+
require.ErrorIs(t, sandboxes.MarkRunning(t.Context(), newSbx), ErrSandboxAlreadyRunning)
522+
523+
live, ok := sandboxes.Get(oldSbx.Runtime.SandboxID)
524+
require.True(t, ok)
525+
require.Same(t, oldSbx, live)
526+
require.Len(t, sandboxes.LifecycleItems(), 1)
527+
}
528+
529+
func TestMapMarkRunningIdempotentForSameLifecycle(t *testing.T) {
530+
t.Parallel()
531+
532+
sandboxes := NewSandboxesMap()
533+
sbx := testMapSandbox(t, "lifecycle-1")
534+
535+
require.NoError(t, sandboxes.MarkRunning(t.Context(), sbx))
536+
require.NoError(t, sandboxes.MarkRunning(t.Context(), sbx))
537+
require.Len(t, sandboxes.Items(), 1)
538+
require.Len(t, sandboxes.LifecycleItems(), 1)
539+
}
540+
541+
func TestSandboxCloseClearsLiveEntryWithoutMarkStopping(t *testing.T) {
542+
t.Parallel()
543+
544+
sandboxes := NewSandboxesMap()
545+
sbx := testMapSandbox(t, "lifecycle-1")
546+
sbx.cleanup = NewCleanup()
547+
sbx.sandboxes = sandboxes
548+
549+
sandboxes.MarkRunning(t.Context(), sbx)
550+
551+
// A crash ends the lifecycle without any explicit MarkStopping. Close must
552+
// clear the live entry too, or the next resume of the same sandbox id will
553+
// lose its registration against this dead entry.
554+
require.NoError(t, sbx.Close(t.Context()))
555+
require.Empty(t, sandboxes.Items())
556+
require.Empty(t, sandboxes.LifecycleItems())
557+
}
558+
559+
func TestSandboxCloseDoesNotRemoveNewerLiveLifecycle(t *testing.T) {
560+
t.Parallel()
561+
562+
sandboxes := NewSandboxesMap()
563+
oldSbx := testMapSandbox(t, "lifecycle-old")
564+
oldSbx.cleanup = NewCleanup()
565+
oldSbx.sandboxes = sandboxes
566+
newSbx := testMapSandbox(t, "lifecycle-new")
567+
568+
sandboxes.MarkRunning(t.Context(), oldSbx)
569+
require.True(t, sandboxes.MarkStopping(t.Context(), oldSbx.Runtime.SandboxID, oldSbx.LifecycleID))
570+
require.NoError(t, sandboxes.MarkRunning(t.Context(), newSbx))
571+
572+
// The old lifecycle's Close must not evict the newer live lifecycle.
573+
require.NoError(t, oldSbx.Close(t.Context()))
574+
575+
live, ok := sandboxes.Get(newSbx.Runtime.SandboxID)
576+
require.True(t, ok)
577+
require.Same(t, newSbx, live)
578+
}
579+
509580
func testMapSandbox(t *testing.T, lifecycleID string) *Sandbox {
510581
t.Helper()
511582

‎packages/orchestrator/pkg/sandbox/sandbox.go‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,10 @@ type StopReason string
216216
const (
217217
// StopReasonKilled covers Delete and the teardowns the orchestrator does
218218
// itself after an operation leaves the sandbox unusable.
219-
StopReasonKilled StopReason = "killed"
220-
StopReasonPaused StopReason = "paused"
221-
StopReasonCheckpointing StopReason = "checkpointing"
219+
StopReasonKilled StopReason = "killed"
220+
StopReasonPaused StopReason = "paused"
221+
StopReasonCheckpointing StopReason = "checkpointing"
222+
StopReasonRegistrationFailed StopReason = "registration_failed"
222223
// StopReasonCrashed is the absence of a recorded reason: nothing asked the
223224
// sandbox to stop and it went down anyway.
224225
StopReasonCrashed StopReason = "crashed"
@@ -1748,6 +1749,8 @@ func (s *Sandbox) Wait(ctx context.Context) error {
17481749
func (s *Sandbox) Close(ctx context.Context) error {
17491750
err := s.cleanup.Run(ctx)
17501751
if s.sandboxes != nil {
1752+
// A guest exit can reach Close without an explicit MarkStopping.
1753+
s.sandboxes.MarkStopping(context.WithoutCancel(ctx), s.Runtime.SandboxID, s.LifecycleID)
17511754
s.sandboxes.MarkStopped(context.WithoutCancel(ctx), s)
17521755
}
17531756

‎packages/orchestrator/pkg/server/create_duplicate_test.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ func TestMarkSandboxLiveRejectsForeignReservationBeforeHealthChecks(t *testing.T
230230
t.Cleanup(foreign.Release)
231231
sbx := drainTestSandbox(t, "candidate")
232232
require.Error(t, s.markSandboxLive(t.Context(), sbx, foreign))
233+
require.Equal(t, sandbox.StopReasonRegistrationFailed, sbx.GetStopReason())
233234
require.Empty(t, s.sandboxFactory.Sandboxes.Items())
234235
require.NoError(t, owner.MarkRunning(t.Context(), sbx))
235236
}

‎packages/orchestrator/pkg/server/sandboxes.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1665,7 +1665,7 @@ func (s *Server) finishSandboxStart(ctx context.Context, reservation *sandbox.Re
16651665

16661666
func (s *Server) markSandboxLive(ctx context.Context, sbx *sandbox.Sandbox, reservation *sandbox.Reservation) error {
16671667
if err := reservation.MarkRunning(ctx, sbx); err != nil {
1668-
sbx.SetStopReason(sandbox.StopReasonKilled)
1668+
sbx.SetStopReason(sandbox.StopReasonRegistrationFailed)
16691669

16701670
return err
16711671
}

0 commit comments

Comments
 (0)