@@ -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+
509580func testMapSandbox (t * testing.T , lifecycleID string ) * Sandbox {
510581 t .Helper ()
511582
0 commit comments