Second review of the night's own code: a stray clears when a later removal works (N2), the start-up check never removes this process's own copy (N3), one copy per destination at a time (N4), RunTier2 refuses during a restore for every caller (N5), the import refusal no longer advises removal (N9); red-proved, race-clean; README; REPORT; unreleased
gates / gates (push) Successful in 1m40s
gates / gates (push) Successful in 1m40s
This commit is contained in:
@@ -33,7 +33,9 @@ func strayCopySeams(t *testing.T, root string, run func(ctx context.Context, arg
|
||||
fn func(args []string) ([]byte, error)) (*dockerRec, *dockerRec) {
|
||||
t.Helper()
|
||||
runs, lifecycle := &dockerRec{}, &dockerRec{}
|
||||
oldRoot, oldImg, oldRun, oldLog, oldDocker, oldTO := tier2CappedRoot, tier2SelfImage, tier2RunCapped, tier2Logf, tier2Docker, tier2CopyTimeout
|
||||
oldRoot, oldImg, oldRun, oldLog, oldDocker, oldTO, oldRetry := tier2CappedRoot, tier2SelfImage, tier2RunCapped, tier2Logf, tier2Docker, tier2CopyTimeout, tier2RetryEvery
|
||||
tier2RetryEvery = time.Hour
|
||||
tier2RetryOff = true // the background retry never starts in a test (it would reach the real Docker later)
|
||||
tier2CappedRoot = root
|
||||
tier2SelfImage = func() string { return "sha256:abc" }
|
||||
tier2RunCapped = func(ctx context.Context, args []string) ([]byte, int, error) {
|
||||
@@ -49,10 +51,11 @@ func strayCopySeams(t *testing.T, root string, run func(ctx context.Context, arg
|
||||
return fn(args)
|
||||
}
|
||||
tier2Logf = func(string, ...any) {}
|
||||
tier2SetStray(false)
|
||||
tier2ResetForTest()
|
||||
t.Cleanup(func() {
|
||||
tier2CappedRoot, tier2SelfImage, tier2RunCapped, tier2Logf, tier2Docker, tier2CopyTimeout = oldRoot, oldImg, oldRun, oldLog, oldDocker, oldTO
|
||||
tier2SetStray(false)
|
||||
tier2CappedRoot, tier2SelfImage, tier2RunCapped, tier2Logf, tier2Docker, tier2CopyTimeout, tier2RetryEvery = oldRoot, oldImg, oldRun, oldLog, oldDocker, oldTO, oldRetry
|
||||
tier2RetryOff = false
|
||||
tier2ResetForTest()
|
||||
})
|
||||
return runs, lifecycle
|
||||
}
|
||||
@@ -210,3 +213,95 @@ func TestStrayCopy_ARestoreIsRefusedWhileACopyRuns(t *testing.T) {
|
||||
t.Fatal("after the copy a restore must be allowed")
|
||||
}
|
||||
}
|
||||
|
||||
// Review 2026-10-23 night. N2: a stray clears when a later removal of that name works (it used to block every restore
|
||||
// until a restart). N3: the start-up check never removes a copy THIS process runs. N4: a second copy into the same
|
||||
// destination waits for the first instead of removing its container. RED-PROOF: with the stray delete on a successful
|
||||
// retry removed → StrayClears fails; with the `running` skip removed → OwnCopyKept fails; with the per-destination lock
|
||||
// removed → SameDestinationWaits fails — each seen failing 2026-10-23 night.
|
||||
func TestStrayCopy_AStrayClearsWhenALaterRemovalWorks(t *testing.T) {
|
||||
root, src, dst := appDirs(t)
|
||||
fail := true
|
||||
strayCopySeams(t, root, func(context.Context, []string) ([]byte, int, error) { return nil, 0, nil },
|
||||
func(args []string) ([]byte, error) {
|
||||
if fail {
|
||||
return []byte("Error response from daemon: cannot remove"), errors.New("exit 1")
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
_ = rsyncMirror(src, dst)
|
||||
if _, blocked := (&Manager{}).Tier2CopyBlocksRestore(); !blocked {
|
||||
t.Fatal("fixture: an unremovable leftover must refuse restores")
|
||||
}
|
||||
fail = false
|
||||
if !tier2RetryOnce(context.Background()) {
|
||||
t.Fatal("the retry must report nothing left once the removal works")
|
||||
}
|
||||
if _, blocked := (&Manager{}).Tier2CopyBlocksRestore(); blocked {
|
||||
t.Fatal("after the stray is removed, restores must be allowed again — without a controller restart")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStrayCopy_TheStartUpCheckKeepsThisProcessesOwnCopy(t *testing.T) {
|
||||
_, life := strayCopySeams(t, t.TempDir()+"/", nil, func(args []string) ([]byte, error) {
|
||||
if args[0] == "ps" {
|
||||
return []byte("felhom-tier2-copy-mine-00000001\nfelhom-tier2-copy-old-00000002\n"), nil
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
tier2MarkRunning("felhom-tier2-copy-mine-00000001", true)
|
||||
if err := clearLeftoverTier2CopiesOnce(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := life.all()
|
||||
if strings.Contains(got, "rm -f felhom-tier2-copy-mine-00000001") || !strings.Contains(got, "rm -f felhom-tier2-copy-old-00000002") {
|
||||
t.Fatalf("the check must remove only the leftover, never this process's running copy:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStrayCopy_ASecondCopyOfTheSameDestinationWaits(t *testing.T) {
|
||||
root, src, dst := appDirs(t)
|
||||
var mu sync.Mutex
|
||||
order := []string{}
|
||||
release := make(chan struct{})
|
||||
first := true
|
||||
runs, _ := strayCopySeams(t, root, func(context.Context, []string) ([]byte, int, error) {
|
||||
mu.Lock()
|
||||
me := first
|
||||
first = false
|
||||
order = append(order, "start")
|
||||
mu.Unlock()
|
||||
if me {
|
||||
<-release
|
||||
}
|
||||
mu.Lock()
|
||||
order = append(order, "end")
|
||||
mu.Unlock()
|
||||
return nil, 0, nil
|
||||
}, func(args []string) ([]byte, error) {
|
||||
mu.Lock()
|
||||
order = append(order, strings.Join(args, " "))
|
||||
mu.Unlock()
|
||||
return nil, nil
|
||||
})
|
||||
d1, d2 := make(chan error), make(chan error)
|
||||
go func() { d1 <- rsyncMirror(src, dst) }()
|
||||
for len(strings.Split(runs.all(), "\n")) < 1 || runs.all() == "" {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
go func() { d2 <- rsyncMirror(src, dst) }()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
mu.Lock()
|
||||
during := strings.Join(order, "|")
|
||||
mu.Unlock()
|
||||
if strings.Count(during, "rm -f") != 1 {
|
||||
t.Fatalf("while the first copy runs, the second must not touch its container: %s", during)
|
||||
}
|
||||
close(release)
|
||||
if err := <-d1; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := <-d2; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user