fix(floyd-warshall): parallel generate() must use a full barrier (root cause of #13) - #15
Conversation
… corrupting next -> cycles) Root cause behind #13 (the v0.7.1 cycle-guard symptom). generateParallel's per-iteration sync was NOT a real barrier: only the owner of the current pivot row k signaled iteration k+1 (one fetchAdd of thread_count). A fast thread could then enter iteration k+1 and read the NEXT pivot row (k+1) while ITS owner was still updating it in iteration k -> dist/next become mutually inconsistent -> cycles in next -> infinite path reconstruction (hang + OOM, seen loading a 1000-worker flying-platform colony). Fix: every thread signals counter[k+1] += 1 after finishing iteration k, and iteration k+1 only starts once all thread_count threads have arrived (true barrier) -> the next pivot row is guaranteed final. Verified: parallel result is now bit-identical to the scalar reference across 24 seeded 256-node graphs (was 60/60 mismatched with cycles before). Adds that regression test at the test ROOT. NOTE: discovered while writing the test that the zspec suite's nested *Spec tests do NOT execute under `zig build test` on Zig 0.16 (only refAllDecls runs) -- so the regression test lives top-level in tests/root.zig where it actually runs. Filed separately; the existing nested tests need the same treatment or a runner fix. Bumps version to 0.7.2.
PR SummaryHigh Risk Overview Adds a top-level regression test in The diff also includes a large vendored Reviewed by Cursor Bugbot for commit fb43cf5. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Code Review
This pull request introduces several improvements and fixes, including a critical fix for the Floyd-Warshall parallel implementation to ensure proper synchronization using a full barrier, and the addition of a regression test for issue #13. It also adds new examples and integration modules for ECS and FSM patterns, along with a JUnit XML report writer. My review identified that committing the zig-pkg/ directory is unnecessary and should be avoided, and suggested an optimization for the regression test to move memory allocations outside the loop to improve performance.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| @@ -0,0 +1,13 @@ | |||
| .{ | |||
There was a problem hiding this comment.
Committing the fetched dependency package (zig-pkg/) to the repository is unnecessary and discouraged when using Zig's package manager.
Zig's package manager automatically downloads, verifies, and caches dependencies based on the URL and hash declared in the root build.zig.zon. Committing these files bloats the repository size and can lead to maintenance issues (e.g., out-of-sync dependency code).
Consider removing the zig-pkg/ directory from version control and adding it to your .gitignore file.
| for (0..24) |seed| { | ||
| var par = FloydWarshallParallel.init(allocator); | ||
| defer par.deinit(); | ||
| var scal = FloydWarshallScalar.init(allocator); | ||
| defer scal.deinit(); | ||
| par.resize(N); | ||
| try par.clean(); | ||
| scal.resize(N); | ||
| try scal.clean(); |
There was a problem hiding this comment.
Initializing and resizing the par and scal instances inside the loop causes redundant allocations and deallocations of the dist and next matrices (each of size 256 * 256 * 4 bytes = 256 KB) on every iteration.
Since clean() resets the state and reuses the allocated matrices when the capacity is sufficient, we can optimize this by moving the initialization and resizing of par and scal outside the loop.
var par = FloydWarshallParallel.init(allocator);
defer par.deinit();
var scal = FloydWarshallScalar.init(allocator);
defer scal.deinit();
par.resize(N);
scal.resize(N);
for (0..24) |seed| {
try par.clean();
try scal.clean();
Fixes the root cause behind #13 (the v0.7.1 cycle-guard treated the symptom).
Root cause
generateParallel's per-iteration synchronization was not a real barrier: only the owner of the current pivot rowksignaled iterationk+1(a singlefetchAdd(thread_count)). A fast thread could then enter iterationk+1and read the next pivot row (k+1) while its owner was still updating it in iterationk→dist/nextbecome mutually inconsistent → cycles innext→ infinite path reconstruction (hang + OOM, seen loading a 1000-worker flying-platform colony).Fix
Every thread signals
counter[k+1] += 1after finishing iterationk; iterationk+1only starts once allthread_countthreads have arrived (true barrier), so the next pivot row is guaranteed final.Verification
A parallel-vs-scalar harness over 256-node random graphs: before = 60/60 runs mismatched (with cycles); after = 0/60, bit-identical to the scalar reference. Added that as a regression test.
tests/root.zig(top-level), not a nested*Specgroup — because nested spec tests don't actually execute underzig build teston Zig 0.16 (filed separately). Verified the root-level test FAILS with the old barrier and PASSES with the fix (3/3 each).version → 0.7.2. Closes #13.