tests/test_strand_race.cpp's third case, "StrandExecutor recycles a drained strand under the key that asked for it", has the same coarse per-round rendezvous shape that morph#826 fixed in the case above it, and was not touched by that fix.
At tests/test_strand_race.cpp:607 (current master, after #826 lands), each of 3 producer threads waits out its own round with:
while (completed.at(slot).load(std::memory_order_acquire) < (round + 1) * kBurst) {
std::this_thread::yield();
}
across kRounds = 900 rounds, 6 iterations. This is the same mechanism #826 diagnosed for the "keeps one strand per key" case: nothing blocks a spinning thread from being scheduled, so under Valgrind -- which runs one thread at a time and lets a yielding thread take the lock straight back -- this loop burns real instrumented CPU time for the whole round instead of yielding it to the actual (slow, instrumented) work.
Verification status: inferred from reading, not independently measured. I have not reproduced a Valgrind stall on this specific case (this host is macOS/arm64, where Valgrind is not practically usable -- same limitation #826's own triage noted). The mechanism is structurally identical to #826's, which was measured on real CI (job 108010066205: ~45 of 54 minutes; PR #806's run 36127304451: >3h before cancellation) for the sibling case in the same file. Whether this case's 900-round loop stalls CI by a similar or smaller margin is unverified -- it has 3 threads instead of 4, yield() on every iteration rather than every 4096 spins, and no fine-grained stagger loop competing for the same core, so the magnitude could differ from #826's case even though the mechanism does not.
What would change the verdict: a Valgrind CI run showing this case's own wall-clock time (the "Valgrind memcheck" job logs a ── memcheck: tests/morph_tests ── header per suite; per-case timing is not broken out, so a large gap in job log timestamps around this case, the way #826 found for its case, would confirm it) either does or does not show a comparable stall. If a future Valgrind run shows this case completing quickly relative to the rest of morph_tests, this can close as not-a-problem-in-practice; if it shows a similar multi-minute-or-worse gap, the fix is the same shape as #826's (block on a condition variable instead of spinning) applied to this case's wait, not a new mechanism.
Found while implementing #826 (a fork of that PR's work flagged it during /simplify's altitude pass); out of scope for #826 itself, whose issue text and triage both named only the "keeps one strand per key" case.
tests/test_strand_race.cpp's third case,"StrandExecutor recycles a drained strand under the key that asked for it", has the same coarse per-round rendezvous shape that morph#826 fixed in the case above it, and was not touched by that fix.At
tests/test_strand_race.cpp:607(currentmaster, after #826 lands), each of 3 producer threads waits out its own round with:across
kRounds = 900rounds, 6 iterations. This is the same mechanism #826 diagnosed for the "keeps one strand per key" case: nothing blocks a spinning thread from being scheduled, so under Valgrind -- which runs one thread at a time and lets a yielding thread take the lock straight back -- this loop burns real instrumented CPU time for the whole round instead of yielding it to the actual (slow, instrumented) work.Verification status: inferred from reading, not independently measured. I have not reproduced a Valgrind stall on this specific case (this host is macOS/arm64, where Valgrind is not practically usable -- same limitation #826's own triage noted). The mechanism is structurally identical to #826's, which was measured on real CI (job 108010066205: ~45 of 54 minutes; PR #806's run 36127304451: >3h before cancellation) for the sibling case in the same file. Whether this case's 900-round loop stalls CI by a similar or smaller margin is unverified -- it has 3 threads instead of 4,
yield()on every iteration rather than every 4096 spins, and no fine-grained stagger loop competing for the same core, so the magnitude could differ from #826's case even though the mechanism does not.What would change the verdict: a Valgrind CI run showing this case's own wall-clock time (the "Valgrind memcheck" job logs a
── memcheck: tests/morph_tests ──header per suite; per-case timing is not broken out, so a large gap in job log timestamps around this case, the way #826 found for its case, would confirm it) either does or does not show a comparable stall. If a future Valgrind run shows this case completing quickly relative to the rest ofmorph_tests, this can close as not-a-problem-in-practice; if it shows a similar multi-minute-or-worse gap, the fix is the same shape as #826's (block on a condition variable instead of spinning) applied to this case's wait, not a new mechanism.Found while implementing #826 (a fork of that PR's work flagged it during
/simplify's altitude pass); out of scope for #826 itself, whose issue text and triage both named only the "keeps one strand per key" case.