What happens
The Module (own workspace) job fails intermittently on main, independent of what a PR changed. In the last twelve main runs it failed four times:
I noticed it because it fired on my own merge (#128) and not on the next one (#129, an hour later, containing the same code) — but #118 and #119 predate that work entirely, so this is not new.
The failure is always the same assertion:
thread 'host::test::manual_override_outranks_a_paused_gate_and_is_bounded' panicked at
crates/tinymemory-module/src/host_test.rs:405:5:
assertion failed: matches!(core_gate::current_policy(), Policy::Paused { .. })
test result: FAILED. 75 passed; 1 failed
Why
tinymemory_core::scheduler_gate keeps its state in two process-global statics:
static GATE: RwLock<Option<Arc<dyn SchedulerGate>>> = RwLock::new(None);
static MANUAL_OVERRIDE_UNTIL: RwLock<Option<std::time::Instant>> = RwLock::new(None);
manual_override_outranks_a_paused_gate_and_is_bounded drives both — clear_manual_override, set_scheduler_gate, set_manual_override, clear_scheduler_gate — and asserts on current_policy() between the writes. store_policy_wakes_sleepers_only_on_resume, in the same file and so the same test binary, drives a gate too.
libtest runs a binary's tests on parallel threads by default, so the two interleave. The assertion that fails is the first read after set_scheduler_gate(gate), which is exactly what a concurrent clear_scheduler_gate from the other test produces. The RwLocks make each individual access safe; they do nothing about two tests wanting different global state at the same time.
Why it is worth fixing rather than re-running
main's CI is the signal everyone uses to decide whether their own branch broke something. At roughly one failure in three, it cannot answer that question — and the failing job is named for the module workspace, so it reads like a real regression rather than a coin toss.
Possible directions
Not proposing a specific fix, but the usual options for global-state tests:
- serialise the tests that touch the gate behind a shared
Mutex (or the serial_test pattern) so they cannot interleave;
- give the gate a scoped/injectable handle for tests so no global is involved;
- or, cheapest and least satisfying, move the gate tests into their own integration target so they get their own process.
Happy to do whichever the maintainers prefer — I have no context on why the gate is global, and that probably decides which of these is right.
Environment
Observed on GitHub Actions, main, runs 33671477150 (#128) among others. Not reproduced locally — the interleaving is timing-dependent and CI's parallelism differs from a laptop's.
What happens
The
Module (own workspace)job fails intermittently onmain, independent of what a PR changed. In the last twelvemainruns it failed four times:I noticed it because it fired on my own merge (#128) and not on the next one (#129, an hour later, containing the same code) — but #118 and #119 predate that work entirely, so this is not new.
The failure is always the same assertion:
Why
tinymemory_core::scheduler_gatekeeps its state in two process-global statics:manual_override_outranks_a_paused_gate_and_is_boundeddrives both —clear_manual_override,set_scheduler_gate,set_manual_override,clear_scheduler_gate— and asserts oncurrent_policy()between the writes.store_policy_wakes_sleepers_only_on_resume, in the same file and so the same test binary, drives a gate too.libtest runs a binary's tests on parallel threads by default, so the two interleave. The assertion that fails is the first read after
set_scheduler_gate(gate), which is exactly what a concurrentclear_scheduler_gatefrom the other test produces. TheRwLocks make each individual access safe; they do nothing about two tests wanting different global state at the same time.Why it is worth fixing rather than re-running
main's CI is the signal everyone uses to decide whether their own branch broke something. At roughly one failure in three, it cannot answer that question — and the failing job is named for the module workspace, so it reads like a real regression rather than a coin toss.Possible directions
Not proposing a specific fix, but the usual options for global-state tests:
Mutex(or theserial_testpattern) so they cannot interleave;Happy to do whichever the maintainers prefer — I have no context on why the gate is global, and that probably decides which of these is right.
Environment
Observed on GitHub Actions,
main, runs 33671477150 (#128) among others. Not reproduced locally — the interleaving is timing-dependent and CI's parallelism differs from a laptop's.