fix(session): delete two dead accessors, make into_drive fallible
drive() and drive_mut() had ZERO callers — not in libfreemkv, freemkv, autorip, bdemu, keysources or kdb. Deleted rather than converted: dead public API that panics is not an API worth preserving the shape of. into_drive() had two callers and now returns Result. The empty-slot state is reachable through ordinary public use — stage_drive_as_reader moves the drive into the reader slot, and calling into_drive twice moves it out — so the panic was not guarding a caller error. identify() was converted for exactly this reason in this same release; the fix went to one of four public sinks and the other three were left. I deferred this on the assumption the blast radius was large. It was three call sites. Checking beats assuming. Also fixes a REAL FLAKE in the gate, which is worth more than the above. resolve_vid_only_bus_key_gate_reports_true_has_volume_id... failed about one full-suite run in ten while passing every time in isolation. It installed a capturing tracing subscriber to read back the has_volume_id field of a warn. That cannot be made reliable: dispatcher::set_default is THREAD-LOCAL while tracing's callsite-interest cache is GLOBAL. The original author knew, and called rebuild_interest_cache() — necessary but not sufficient. I first serialised every capture in the crate behind one lock (harness::with_captured_tracing, which also removed the same hand-rolled dance from three other sites). Still 1-in-10, because the cache can be re-evaluated against the process-default dispatch rather than the thread-local one. So the predicate is now a named function, handshake_has_volume_id, and the test asserts the VALUE. A boolean does not need a subscriber to check. The gate's hard-error behaviour keeps its own test. Measured: 14 consecutive full-suite runs, 2994 passed, 0 failed. A flaky gate is worse than a missing one — every green after it means less, and this one had been eroding trust in the whole suite.
This commit is contained in:
@@ -265,3 +265,56 @@ fn the_generators_actually_reach_the_parser_bodies() {
|
||||
);
|
||||
println!("mpls reach: {ok}/{total} cases parsed to completion");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Capturing-subscriber serialisation.
|
||||
//
|
||||
// Several tests install a capturing `tracing` subscriber to assert on a log
|
||||
// line's FIELDS — the only observable for a diagnostic that does not change a
|
||||
// return value. Doing that safely needs two things that pull in opposite
|
||||
// directions:
|
||||
//
|
||||
// * `dispatcher::set_default` / `subscriber::with_default` are THREAD-LOCAL.
|
||||
// * `tracing` caches per-callsite "is any subscriber interested?" GLOBALLY, the
|
||||
// first time each callsite fires. A callsite that already fired under the
|
||||
// process default (a no-op) is cached as "not interested" forever, so the
|
||||
// event never reaches a later capturing subscriber.
|
||||
//
|
||||
// The fix for the second is `rebuild_interest_cache()`. But that is global too,
|
||||
// so two tests doing this on different threads race: one rebuilds the cache to
|
||||
// "interested" for its own thread-local dispatch, the other rebuilds it back
|
||||
// while the first is mid-flight, and the first silently observes nothing.
|
||||
//
|
||||
// That is not hypothetical — it is a real intermittent failure of
|
||||
// `resolve_vid_only_bus_key_gate_reports_true_has_volume_id_when_vid_nonzero`,
|
||||
// which passes in isolation every time and fails under the full parallel suite.
|
||||
// The test carried a comment describing the hazard and a `rebuild_interest_cache`
|
||||
// call intended to fix it; the call is necessary but not sufficient.
|
||||
//
|
||||
// So capture is serialised process-wide here. Four tests across two modules were
|
||||
// each hand-rolling the same dance; one of them getting it subtly wrong is
|
||||
// exactly the drift a shared helper removes.
|
||||
static CAPTURE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
|
||||
/// Run `f` with `subscriber` installed as the thread-local `tracing` dispatch,
|
||||
/// serialised against every other capture in this crate.
|
||||
///
|
||||
/// The interest cache is rebuilt on the way in (so a callsite already poisoned
|
||||
/// by an earlier no-op dispatch is reconsidered) and on the way out (so the
|
||||
/// next test does not inherit a cache built for a subscriber that is gone).
|
||||
pub(crate) fn with_captured_tracing<S, F, R>(subscriber: S, f: F) -> R
|
||||
where
|
||||
S: tracing::Subscriber + Send + Sync + 'static,
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
// Poisoning is irrelevant: the guard protects ordering, not data, and a
|
||||
// panicking test has already failed.
|
||||
let _lock = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let dispatch = tracing::Dispatch::new(subscriber);
|
||||
let guard = tracing::dispatcher::set_default(&dispatch);
|
||||
tracing::callsite::rebuild_interest_cache();
|
||||
let out = f();
|
||||
drop(guard);
|
||||
tracing::callsite::rebuild_interest_cache();
|
||||
out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user