From 5b6bf0f82c880bcf18f3dcbc2d827795e800915f Mon Sep 17 00:00:00 2001 From: CMGS Date: Sun, 4 Oct 2026 22:44:08 +0800 Subject: [PATCH] test(pool): idle-sweep tests wait for the sweep to finish before they end A sweep publishes HibernateSnap under m.mu, then commits claims.json and appends the usage event outside it, so a test that stopped at hibernated == 1 could return while those writes ran and race the TempDir cleanup (CI: "unlinkat ...: directory not empty"). sweepIdle runs one sweep and waits for its goroutine to exit, which follows every write. --- sandboxd/pool/archive_test.go | 9 ++--- sandboxd/pool/idle_test.go | 51 ++++++++++++++++------------ sandboxd/pool/volume_capture_test.go | 3 +- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/sandboxd/pool/archive_test.go b/sandboxd/pool/archive_test.go index 8b9ff029..962de265 100644 --- a/sandboxd/pool/archive_test.go +++ b/sandboxd/pool/archive_test.go @@ -193,8 +193,10 @@ func TestArchiveLifecycleRoundTrip(t *testing.T) { id, token, origVM := sb.ID, sb.Token, sb.VMName backdate(m, sb, 5*time.Second) - m.idleOnce(t.Context()) - waitFor(t, func() bool { return hibernated(m) == 1 }) + sweepIdle(t, m) + if hibernated(m) != 1 { + t.Fatal("idle claim was not hibernated") + } m.archiveOnce(t.Context()) waitFor(t, func() bool { return archivedCount(m) == 1 }) @@ -695,8 +697,7 @@ func TestIdleOnceSkipsArchived(t *testing.T) { hibBefore := eng.hibernateCount() backdate(m, sb, time.Hour) - m.idleOnce(t.Context()) - waitFor(t, func() bool { return !m.idleSweep.Load() }) + sweepIdle(t, m) if got := eng.hibernateCount(); got != hibBefore { t.Errorf("idle sweep hibernated an archived claim: %d→%d", hibBefore, got) } diff --git a/sandboxd/pool/idle_test.go b/sandboxd/pool/idle_test.go index 89fdf209..3ba7fe24 100644 --- a/sandboxd/pool/idle_test.go +++ b/sandboxd/pool/idle_test.go @@ -21,17 +21,18 @@ func TestIdleOnceHibernatesPastThreshold(t *testing.T) { m := newTestManager(t, eng, config.PoolSpec{PoolKey: testKey, Warm: 1, IdleHibernateSeconds: 1}) sb := mustClaim(t, m, testKey) - m.idleOnce(t.Context()) + sweepIdle(t, m) if hibernated(m) != 0 { t.Fatal("fresh claim hibernated before its threshold") } backdate(m, sb, 2*time.Second) - m.idleOnce(t.Context()) - waitFor(t, func() bool { return hibernated(m) == 1 }) + sweepIdle(t, m) + if hibernated(m) != 1 { + t.Fatal("idle claim was not hibernated") + } - m.idleOnce(t.Context()) - waitFor(t, func() bool { return !m.idleSweep.Load() }) + sweepIdle(t, m) if hibernated(m) != 1 { t.Error("second sweep disturbed the hibernated claim") } @@ -46,8 +47,7 @@ func TestIdleOncePolicyScope(t *testing.T) { sb := mustClaim(t, m, testKey) backdate(m, sb, time.Hour) - m.idleOnce(t.Context()) - waitFor(t, func() bool { return !m.idleSweep.Load() }) + sweepIdle(t, m) if hibernated(m) != 0 { t.Fatal("pooled key without the policy was idle-hibernated by the node default") } @@ -58,8 +58,10 @@ func TestIdleOncePolicyScope(t *testing.T) { t.Fatalf("provision: %v", err) } backdate(m, sb2, time.Hour) - m.idleOnce(t.Context()) - waitFor(t, func() bool { return hibernated(m) == 1 }) + sweepIdle(t, m) + if hibernated(m) != 1 { + t.Fatal("idle claim was not hibernated") + } }) } @@ -75,8 +77,7 @@ func TestActivityStampsBlockIdleSweep(t *testing.T) { t.Fatalf("WakeAgentSocket: %v", err) } done() - m.idleOnce(t.Context()) - waitFor(t, func() bool { return !m.idleSweep.Load() }) + sweepIdle(t, m) if hibernated(m) != 0 { t.Fatal("active claim hibernated despite a fresh data-plane stamp") } @@ -94,22 +95,22 @@ func TestHeldConnectionBlocksIdleSweep(t *testing.T) { } backdate(m, sb, time.Hour) - m.idleOnce(t.Context()) - waitFor(t, func() bool { return !m.idleSweep.Load() }) + sweepIdle(t, m) if hibernated(m) != 0 { t.Fatal("claim with an open connection was idle-hibernated") } done() - m.idleOnce(t.Context()) - waitFor(t, func() bool { return !m.idleSweep.Load() }) + sweepIdle(t, m) if hibernated(m) != 0 { t.Fatal("idle clock did not restart when the connection closed") } backdate(m, sb, 2*time.Second) - m.idleOnce(t.Context()) - waitFor(t, func() bool { return hibernated(m) == 1 }) + sweepIdle(t, m) + if hibernated(m) != 1 { + t.Fatal("idle claim was not hibernated") + } }) } @@ -141,8 +142,7 @@ func TestEgressRequestInFlightBlocksIdleSweep(t *testing.T) { }() waitFor(t, sb.Busy) backdate(m, sb, time.Hour) - m.idleOnce(t.Context()) - waitFor(t, func() bool { return !m.idleSweep.Load() }) + sweepIdle(t, m) if hibernated(m) != 0 { t.Fatal("claim with an egress request in flight was idle-hibernated") } @@ -152,14 +152,21 @@ func TestEgressRequestInFlightBlocksIdleSweep(t *testing.T) { t.Fatalf("request through the proxy: %v", err) } waitFor(t, func() bool { return !sb.Busy() }) - m.idleOnce(t.Context()) - waitFor(t, func() bool { return !m.idleSweep.Load() }) + sweepIdle(t, m) if hibernated(m) != 0 { t.Fatal("idle clock did not restart when the egress request ended") } backdate(m, sb, time.Hour) + sweepIdle(t, m) + if hibernated(m) != 1 { + t.Fatal("idle claim was not hibernated") + } +} + +func sweepIdle(t *testing.T, m *Manager) { + t.Helper() m.idleOnce(t.Context()) - waitFor(t, func() bool { return hibernated(m) == 1 }) + waitFor(t, func() bool { return !m.idleSweep.Load() }) } func backdate(m *Manager, sb *types.Sandbox, by time.Duration) { diff --git a/sandboxd/pool/volume_capture_test.go b/sandboxd/pool/volume_capture_test.go index e330427b..d8a3d92d 100644 --- a/sandboxd/pool/volume_capture_test.go +++ b/sandboxd/pool/volume_capture_test.go @@ -125,8 +125,7 @@ func TestIdleOnceSkipsVolumeClaim(t *testing.T) { sb.Volumes = []types.Volume{{Name: "dataset", Mount: "/datasets"}} backdate(m, sb, 2*time.Second) - m.idleOnce(t.Context()) - waitFor(t, func() bool { return !m.idleSweep.Load() }) + sweepIdle(t, m) if len(eng.hibernates) != 0 || hibernated(m) != 0 { t.Errorf("idle sweep hibernated volume claim: engine=%v count=%d", eng.hibernates, hibernated(m)) }