diff --git a/crates/ecstore/src/services/tier/tier.rs b/crates/ecstore/src/services/tier/tier.rs index f48f58fc0..8d80f37e9 100644 --- a/crates/ecstore/src/services/tier/tier.rs +++ b/crates/ecstore/src/services/tier/tier.rs @@ -17579,6 +17579,66 @@ mod tests { assert!(current.tiers.contains_key("COLD-B")); } + async fn wait_for_reference_proof_barrier( + barrier: &TierDriverBuildBarrier, + update: &mut tokio::task::JoinHandle>, + ) -> std::result::Result<(), String> { + tokio::select! { + biased; + result = &mut *update => Err(format!("tier update exited before the reference proof barrier: {result:?}")), + () = barrier.arrived.notified() => Ok(()), + () = tokio::time::sleep(Duration::from_secs(30)) => { + // Aborting the caller does not stop its owned mutation task. + // Let a late arrival pass the test-only barrier. + barrier.release.add_permits(1); + update.abort(); + Err("timed out waiting for the reference proof barrier".to_string()) + } + } + } + + #[tokio::test] + #[serial_test::serial] + async fn reference_proof_barrier_reports_update_failure_before_arrival() { + let manager = TierConfigMgr::new(); + let store = Arc::new(CasConfigStore::default()); + let mut persisted = empty_mgr(); + persisted.tiers.insert("COLD-A".to_string(), build_rustfs_tier("COLD-A")); + persisted + .save_tiering_config_if_current(store.clone(), None) + .await + .expect("early update failure fixture should persist"); + let barrier = tier_reference_proof_test_barrier(); + let scoped_barrier = barrier.clone(); + let factory: TierDriverTestFactory = + Arc::new(|_| Err(AdminError::msg("injected driver initialization failure before reference proof"))); + let mut update = tokio::spawn(async move { + TIER_REFERENCE_PROOF_TEST_BARRIER + .scope( + scoped_barrier, + TIER_DRIVER_TEST_FACTORY.scope( + factory, + TIER_MUTATION_TEST_PEERS.scope( + Vec::new(), + TierConfigMgr::update_candidate_with_config_lock( + &manager, + store, + TierCandidateMutation::Remove("COLD-A".to_string(), true), + ), + ), + ), + ) + .await + }); + + let err = tokio::time::timeout(Duration::from_secs(5), wait_for_reference_proof_barrier(&barrier, &mut update)) + .await + .expect("an early update failure should be observed without waiting for the barrier deadline") + .expect_err("a failed update cannot reach the reference proof barrier"); + assert!(err.contains("Mutation"), "{err}"); + assert!(err.contains("injected driver initialization failure before reference proof"), "{err}"); + } + #[tokio::test] #[serial_test::serial] async fn reference_proof_rejects_a_changed_prepared_fence_revision_before_publish() { @@ -17599,7 +17659,7 @@ mod tests { let scoped_barrier = barrier.clone(); let update_manager = manager.clone(); let update_store = store.clone(); - let update = tokio::spawn(async move { + let mut update = tokio::spawn(async move { TIER_REFERENCE_PROOF_TEST_BARRIER .scope( scoped_barrier, @@ -17614,7 +17674,9 @@ mod tests { ) .await }); - barrier.arrived.notified().await; + wait_for_reference_proof_barrier(&barrier, &mut update) + .await + .expect("tier update should reach the reference proof barrier"); let unrelated = prepared_remove_intent("COLD-B", uuid::Uuid::from_u128(0x2237)); TierConfigMgr::apply_prepared_mutation_intent_block(&manager, &unrelated) @@ -17622,8 +17684,9 @@ mod tests { .expect("an unrelated prepared fence should advance the runtime revision"); barrier.release.add_permits(1); - let err = update + let err = tokio::time::timeout(Duration::from_secs(30), update) .await + .expect("tier update should finish after the reference proof barrier releases") .expect("tier update task should join") .expect_err("a reference proof cannot authorize publication across a fence revision change"); let TierConfigUpdateError::Publish(err) = err else {