package dockerexec import ( "os" "strings" "testing" "time" ) // D48 — a restore holds the image-work lock from its pre-pull until its last start. While it is held the clean-up // cannot run, and a compose up inside the hold does not block. func TestD48_HoldImageWorkBlocksCleanupAndNests(t *testing.T) { end := HoldImageWork() if _, ok := TryImageCleanup(); ok { t.Fatal("the image clean-up must not run while a restore holds its images") } done := make(chan struct{}) go func() { defer close(done) BeginImageWork([]string{"compose", "up", "-d"})() }() select { case <-done: case <-time.After(2 * time.Second): t.Fatal("a compose up inside the restore's hold must not block") } end() unlock, ok := TryImageCleanup() if !ok { t.Fatal("after the hold the clean-up runs again") } unlock() } // The nesting is safe only while the one exclusive taker never WAITS (a waiting writer would block a nested RLock). // Pin it: the package takes the lock exclusively nowhere but TryLock. func TestD48_TheOnlyExclusiveTakerNeverWaits(t *testing.T) { b, err := os.ReadFile("imagework.go") if err != nil { t.Fatal(err) } if strings.Contains(string(b), "imageWorkMu.Lock()") { t.Fatal("a blocking exclusive Lock() on imageWorkMu would deadlock a restore's nested shared hold (HoldImageWork)") } }