diff --git a/benchmarks/aft-search/slice-descriptors/land-flake-batch-20.json b/benchmarks/aft-search/slice-descriptors/land-flake-batch-20.json new file mode 100644 index 000000000..d841ae4fc --- /dev/null +++ b/benchmarks/aft-search/slice-descriptors/land-flake-batch-20.json @@ -0,0 +1,12 @@ +{ + "slice_class": "non_ranking", + "targeted_mechanism": "none", + "kind": "harness", + "fixtures": [ + "harness-goldens" + ], + "slices": [ + "flake-batch-20" + ], + "note": "Test-only: views-off eviction test waits for an idle root. No changed path matches RANKING_FENCE_PREFIXES." +} diff --git a/crates/aft/src/gc/mod.rs b/crates/aft/src/gc/mod.rs index 12e44ea7a..a325a82d1 100644 --- a/crates/aft/src/gc/mod.rs +++ b/crates/aft/src/gc/mod.rs @@ -139,7 +139,7 @@ fn sweep_planes_with_sidecars( let cancelled_for_bind = || cancelled() || crate::storage_retention::bind_pending(request.durable_storage); let control = SweepControl { - deadline: Instant::now() + Duration::from_secs(5), + deadline: Instant::now() + sweep_budget(), cancelled: &cancelled_for_bind, }; control.check()?; @@ -701,6 +701,18 @@ type BeforeBlobDelete = Box; #[cfg(test)] thread_local! { static BEFORE_BLOB_DELETE: std::cell::RefCell> = const { std::cell::RefCell::new(None) }; + static SWEEP_BUDGET_FOR_TEST: std::cell::Cell> = const { std::cell::Cell::new(None) }; +} + +/// One sweep's wall-clock budget. Tests that check what a sweep deletes, not +/// how long it may take, can raise it so a loaded CI runner cannot cut them +/// short. +fn sweep_budget() -> Duration { + #[cfg(test)] + if let Some(budget) = SWEEP_BUDGET_FOR_TEST.with(std::cell::Cell::get) { + return budget; + } + Duration::from_secs(5) } #[cfg(test)] @@ -782,6 +794,9 @@ mod storage_retention_tests { hook_calls.set(hook_calls.get() + 1); })) }); + // This test checks the delete transaction, not the 5 s budget, which + // a loaded Windows runner overran on this three-row fixture. + SWEEP_BUDGET_FOR_TEST.with(|budget| budget.set(Some(Duration::from_secs(60)))); let report = sweep(SweepRequest { durable_storage: temp.path(), storage: temp.path(), @@ -793,8 +808,9 @@ mod storage_retention_tests { retained_keys: BTreeSet::from([[1_u8; 32]]), ..Default::default() }, - }) - .unwrap(); + }); + SWEEP_BUDGET_FOR_TEST.with(|budget| budget.set(None)); + let report = report.unwrap(); BEFORE_BLOB_DELETE.with(|slot| slot.borrow_mut().take()); assert_eq!(calls.get(), 2); assert_eq!(report.deleted_blobs, 2); diff --git a/crates/aft/src/subc/bash.rs b/crates/aft/src/subc/bash.rs index c43f95394..85c2ad85e 100644 --- a/crates/aft/src/subc/bash.rs +++ b/crates/aft/src/subc/bash.rs @@ -8152,7 +8152,11 @@ mod grant_path_tests { .build() .expect("current-thread test runtime"); runtime.block_on(async { - let (mut startup, done) = startup_case(held_before_spawn_stub, 80).await; + // The test fires the deadline once the stub holds the sandbox + // profile step. The window is only a backstop and must outlast + // executor admission: at 80 ms a loaded macOS runner was still + // admitting, so the backstop fired first and named admission. + let (mut startup, done) = startup_case(held_before_spawn_stub, 3_000).await; let response = done.response_for_test(); assert_eq!(response.data["code"], "bash_start_deadline"); let message = response.data["message"] diff --git a/crates/aft/tests/integration/per_checkout_semantic_runtime.rs b/crates/aft/tests/integration/per_checkout_semantic_runtime.rs index a312e55ec..5960fe801 100644 --- a/crates/aft/tests/integration/per_checkout_semantic_runtime.rs +++ b/crates/aft/tests/integration/per_checkout_semantic_runtime.rs @@ -830,6 +830,16 @@ fn evict_until_lane_released(ctx: &AppContext) { thread::sleep(Duration::from_millis(20)); } } +/// Evicts the root's artifacts once it is idle. Readiness can be reported +/// while background work that settled it is still finishing, and eviction +/// refuses a root that is not idle yet, so retry until it accepts. +fn evict_once_idle(ctx: &AppContext) { + let deadline = Instant::now() + DEADLINE; + while !ctx.evict_idle_artifacts() { + assert!(Instant::now() < deadline, "the root never became idle"); + thread::sleep(Duration::from_millis(20)); + } +} /// Idle eviction drops the lexical lane too, and both lanes reload by design. /// Reloads the lexical lane through a grep, which reloads only that lane, and /// waits until grep answers from a ready index, so the next search has a @@ -927,7 +937,7 @@ fn views_off_search_after_idle_eviction_reports_the_index_reloading() { let storage = tempfile::tempdir().unwrap(); let ctx = configure(&repo.main, storage.path(), &server, false); wait_legacy_ready(&ctx); - assert!(ctx.evict_idle_artifacts(), "the root must be idle"); + evict_once_idle(&ctx); // As in the views-on test: a search would refuse while the text index is // still reloading too, so reload it first and keep the check on semantic. reload_trigram_after_eviction(&ctx);