Skip to content

Make the dispatch test fixture's temp root collision-proof - #176

Merged
MJohnson459 merged 2 commits into
mainfrom
fix-dispatch-fixture-root
Aug 14, 2026
Merged

MJohnson459 merged 2 commits into
mainfrom
fix-dispatch-fixture-root

Conversation

@MJohnson459

Copy link
Copy Markdown
Contributor

fixture_toml in crates/voro/src/dispatch.rs named its temporary root from
the process id and a SystemTime nanosecond stamp. Both are shared across the
test binary's threads and the clock is not fine-grained enough to separate two
fixtures built in the same instant, so two tests running in parallel could land
on the same directory and the same scratch database — the intermittent
UNIQUE constraint failed: projects.name seen in the dispatch suite.

The root name now carries an atomic counter incremented per fixture, alongside
the existing pid and stamp: the counter separates fixtures within a run
whatever the clock resolution, the stamp still separates reruns of a recycled
pid. The naming moved into a fixture_root() helper in the test module;
fixture and fixture_toml keep their signatures and behaviour.

Two tests cover it. fixtures_built_at_once_on_many_threads_get_distinct_roots
builds eight fixtures concurrently behind a barrier and asserts the roots are
distinct, as the acceptance criteria ask. Eight proved too few to catch a name
that leans on the clock alone, so roots_named_back_to_back_on_many_threads_are_all_distinct
names four thousand roots across eight threads with nothing between them.

Verified: with the counter swapped for a plain load, the second test fails with
29 collisions out of 4000 — which confirms the original flake's mechanism
rather than assuming it — and passes with the counter restored.
cargo test --workspace was run five times over and passed every run, with
cargo clippy --workspace --all-targets -- -D warnings and
cargo fmt --all -- --check clean. Test code only; no production code changed.

Follow-up filed as task #455: twenty other test temp roots are named the same
way, most single-caller and so safe today, but the shared helpers are not
robust by construction.

`fixture_toml` named its temporary root from the process id and a
`SystemTime` nanosecond stamp. Both are shared across the test binary's
threads, and the clock is not fine-grained enough to separate two
fixtures built in the same instant, so two tests running in parallel
could land on the same directory and the same scratch database. The
symptom was an intermittent `UNIQUE constraint failed: projects.name`,
seen once in the dispatch suite and passing on the retry — the kind of
failure that teaches the operator to re-run rather than to read.

The name now carries an atomic counter as well, which the test module
increments per fixture. The clock still separates reruns of a recycled
pid; the counter separates fixtures within one run, whatever the clock
resolution.

Two tests cover it. The first builds eight fixtures concurrently behind
a barrier and asserts distinct roots. Eight is too few to catch a name
that leans on the clock alone, so the second names four thousand roots
across eight threads with nothing between them: with the counter
replaced by a plain load, that test fails with 29 collisions, which is
also the mechanism of the original flake.

Test code only. `cargo test --workspace` passes five runs over, with
clippy and fmt clean.
The counter added alongside the stamp reads as unnecessary — a
nanosecond is short enough that nothing should share one — and the
comment asserted the collision without saying what causes it, leaving
the next reader to make the same objection.

Measured on this workstation: 100000 consecutive `SystemTime::now()`
reads on one thread never repeat, so the objection holds sequentially.
The clock does not advance a nanosecond at a time, though; it steps
every 20-30ns. On one thread each read is ordered after the last and so
lands on a later step, but threads have no such ordering, and any two
reading inside one step read the same number: 4000 reads across eight
threads gave 1493 duplicates. A test binary runs its tests on threads,
which is exactly that case.

The comments now carry the mechanism and the measurement, and the note
on the four-thousand-root test says what the eight-fixture test above it
cannot catch: those eight sit in `git init` long enough to drift onto
separate steps, so they pass even on a clock-only name.

Comments only; no code changed.
@MJohnson459
MJohnson459 merged commit 8e7d2d4 into main Aug 14, 2026
7 checks passed
@MJohnson459
MJohnson459 deleted the fix-dispatch-fixture-root branch August 14, 2026 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant