CultLib/native/GameCult.Mesh.Quic.Native/tests/cultmesh_quic_native_tests.cpp

1305 lines
68 KiB
C++
Raw Normal View History

// Scenario runner for the bridge's runtime lifetime rules.
//
// This is the one part of the bridge a host cannot check by reading: whether
// `cultmesh_quic_runtime_close` is safe against host threads that are inside the
// library when it starts. Everything here drives that path and nothing else. It
// opens a runtime, parks pollers in `cultmesh_quic_next_event`, closes the
// runtime underneath them, and asserts what the header promises: every parked
// poller returns, and none of them touches the runtime after it is gone.
//
// It consumes the bridge through include/cultmesh_quic_native.h — the same
// contract a host has — and nothing else. No MsQuic header, no internals.
//
// The failures it exists to catch are not visible in the return values, because
// a use-after-free is not an error code. Two things make them visible instead:
// a sanitizer build (ThreadSanitizer for the notify/destroy race, AddressSanitizer
// for the freed-runtime dereference), and `CULTMESH_QUIC_DEBUG_ASSERTS`, which
// makes the bridge state the quiesce invariant itself rather than leaving it to
// be caught by an unlucky thread schedule.
//
// cultmesh_quic_native_tests closerace [iterations] [pollers]
// cultmesh_quic_native_tests holdclose [iterations] [pollers]
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
// cultmesh_quic_native_tests polltimeout [iterations]
// cultmesh_quic_native_tests pollbusy [iterations]
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
// cultmesh_quic_native_tests latecall [iterations]
// cultmesh_quic_native_tests holdtimeout [iterations]
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
// cultmesh_quic_native_tests waitseam [iterations]
// cultmesh_quic_native_tests pollhammer [iterations]
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
// cultmesh_quic_native_tests zerotimeout [iterations]
// cultmesh_quic_native_tests payloadfit [iterations]
//
// `closerace` hunts the race: many pollers, many iterations, and whatever the
// schedule gives. `holdclose` takes the guessing out of it — the bridge's
// development seam (section 7 of the header) holds the pollers inside the
// library until this scenario releases them, and reports how many host calls the
// bridge actually counted, so the scenario asserts the quiesce instead of
// assuming its own fixture worked. It needs CULTMESH_QUIC_DEBUG_ASSERTS.
//
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
// In both of those the wait is ended by the close, so the host's timeout governs
// nothing either of them can see, and a bridge waiting on a constant of its own
// passes both. `polltimeout` is the one that lets the timeout govern the return
// and measures it, and `pollbusy` measures it again with the host's other thread
// calling in throughout, which wakes the wait. `latecall` covers the other half
// of section 4: a call that races the start of a close is refused rather than
// counted behind its wait.
// `holdtimeout` covers the seam's own rule, which nothing else is in a position
// to see: the hold parks a call the wait woke and not one whose own timeout
// expired, so the quiesce the hold scenarios assert is the bridge's doing and
// not the fixture's.
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
//
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
// Self's ruling of 2026-09-22, after seven Soul passes against `polltimeout`,
// `pollbusy` and `holdtimeout` alone: a wall-clock probe cannot tell the host's
// timeout from any function of it that is the identity at the probe's own
// value, because there is always another function that matches. `waitseam`
// puts the observation where the rule is decided instead of where its effect
// eventually shows: it reads `cultmesh_quic_debug_last_wait_ms`, the bridge's
// own account of what it handed its condition wait, and asserts equality
// against the host's argument, deterministically, with no timing tolerance.
// `polltimeout`, `pollbusy` and `holdtimeout` stay only as generous-margin
// proof that the recorded wait is really waited and not merely recorded.
// `pollhammer` covers the wait's predicate the same way `pollbusy` used to try
// to: one thread hammers every gate-touching entry point that cannot itself
// queue an event, in a yield loop, while another holds a single idle poll, and
// the poll must still stay for its timeout.
//
// Exit code 0 means every assertion held. Anything else, including a sanitizer
// abort or the bridge's own assertion, is a failure. Under ThreadSanitizer run
// it with `TSAN_OPTIONS=halt_on_error=1`, or a reported race leaves the exit
// code to the last thing that set it.
#include <cultmesh_quic_native.h>
Probe the host's timeout at 15, 200 and 7300 ms, and stress the tolerance Soul's sixth pass (N2): with probes at 150, 200 and 5000 ms, `min(t,5000)`, `min(t,4990)`, `t*11/10` and `t+70` survived both targets, `max(t,220)` survived linux-x64, and `max(t,250)` and `t+80` died by 22 and 2 ms. "5000 is above any plausible ceiling" was wrong: 5000 is a round ceiling. - A 15 ms probe, asked ten times with the fastest checked against 30 ms. It is the only place a floor or an added constant shows. Scheduler delay only adds, so noise has to delay all ten attempts to fail the bridge. - The long probe is 7300 ms, which is not a round number. - A single poll gets 60 ms plus a sixteenth of the timeout. - Each timed scenario prints its worst overshoot per probe. - Three harness entries: a round clamp `min(t,5000)`, a floor `max(t,100)` and an offset `t+100`. Stressed with two busy threads per logical CPU (16 on 8), five rounds of every timed scenario on both targets. At normal priority, Windows put a single poll up to 127 ms late and the fastest of ten 15 ms attempts up to 87 ms late, and a 45 ms tolerance failed the unmutated bridge in four rounds of five. The timed scenarios therefore raise their own process to HIGH_PRIORITY_CLASS on win32-x64, which needs no privilege. With that, the worst single-poll overshoot was 15 ms on win32-x64 and 41 ms on linux-x64, at normal priority there. The fastest 15 ms attempt was 0 ms late on both. The limit in source is measured, not asserted. Each boundary mutant was compiled in and run on both targets: - `t+25` survived and `t+40` died; - `max(t,40)` survived and `max(t,50)` died, on linux-x64 by 5 ms; - `t*106/100` survived and `t*108/100` died; - `min(t,7285)` survived and `min(t,7275)` died. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:59:37 +02:00
#include <algorithm>
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
#include <array>
#include <atomic>
#include <chrono>
Probe the host's timeout at 15, 200 and 7300 ms, and stress the tolerance Soul's sixth pass (N2): with probes at 150, 200 and 5000 ms, `min(t,5000)`, `min(t,4990)`, `t*11/10` and `t+70` survived both targets, `max(t,220)` survived linux-x64, and `max(t,250)` and `t+80` died by 22 and 2 ms. "5000 is above any plausible ceiling" was wrong: 5000 is a round ceiling. - A 15 ms probe, asked ten times with the fastest checked against 30 ms. It is the only place a floor or an added constant shows. Scheduler delay only adds, so noise has to delay all ten attempts to fail the bridge. - The long probe is 7300 ms, which is not a round number. - A single poll gets 60 ms plus a sixteenth of the timeout. - Each timed scenario prints its worst overshoot per probe. - Three harness entries: a round clamp `min(t,5000)`, a floor `max(t,100)` and an offset `t+100`. Stressed with two busy threads per logical CPU (16 on 8), five rounds of every timed scenario on both targets. At normal priority, Windows put a single poll up to 127 ms late and the fastest of ten 15 ms attempts up to 87 ms late, and a 45 ms tolerance failed the unmutated bridge in four rounds of five. The timed scenarios therefore raise their own process to HIGH_PRIORITY_CLASS on win32-x64, which needs no privilege. With that, the worst single-poll overshoot was 15 ms on win32-x64 and 41 ms on linux-x64, at normal priority there. The fastest 15 ms attempt was 0 ms late on both. The limit in source is measured, not asserted. Each boundary mutant was compiled in and run on both targets: - `t+25` survived and `t+40` died; - `max(t,40)` survived and `max(t,50)` died, on linux-x64 by 5 ms; - `t*106/100` survived and `t*108/100` died; - `min(t,7285)` survived and `min(t,7275)` died. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:59:37 +02:00
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <thread>
#include <vector>
namespace {
Make closerace's poll timeout outlast its own settle budget Under CPU starvation (16 burners on 8 logical CPUs), all 5 rounds failed at iteration 0 or 1 with "256 of 256 pollers returned before the close began, so they were never in the race". The fixture contradicted itself: the asserts-on settle waits up to 30s (plus a 50ms remainder) for every poller to be counted inside the library, but each poller's own next_event wait used a 5s timeout, so the first pollers timed out for real while the settle was still waiting on the last ones. The race never ran; the fixture just gave up on itself. kPollTimeoutMs is now derived from the settle budget it must outlast in every build configuration (asserts-on's counted deadline plus its fixed remainder, and the no-seam #else's longer fixed window), named side by side so they cannot drift apart again. A long poll timeout costs nothing on a passing run, since the close is what actually ends each wait. Also give the fixture's own failure to settle a distinct report. If counted_deadline expires before every poller is counted, CloseRaceOnce now says "fixture could not settle: N of M pollers counted inside after 30000 ms" instead of proceeding to a close that blames the pollers for a race the fixture never set up. holdclose, latecall and holdtimeout use the same counted-deadline idiom but are not the same defect: their pollers are parked behind cultmesh_quic_debug_hold_calls, so they cannot time out for real while the settle waits, and were left alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 13:40:42 +02:00
// `CloseRaceOnce`'s own settle budget, named here — ahead of `kPollTimeoutMs`
// below — so the poll timeout can be derived from it instead of drifting apart
// as a second freestanding number. Under CPU starvation the settle can spend
// this whole budget waiting for every poller to be counted inside the library
// before the close begins; a poll timeout shorter than that lets the first
// pollers time out for real while the fixture is still waiting on the last
// ones, which is exactly the failure this scenario exists to catch, not the
// one it is supposed to be running.
constexpr int32_t kCloseRaceCountedDeadlineMs = 30000; // asserts-on: cap on the peak_calls wait
constexpr int32_t kCloseRaceAssertsSettleMs = 50; // asserts-on: fixed remainder after counted
constexpr int32_t kCloseRaceNoSeamSettleMs = 500; // no-seam #else: fixed settle window
constexpr int32_t kCloseRaceSettleBudgetMs =
(kCloseRaceCountedDeadlineMs + kCloseRaceAssertsSettleMs) > kCloseRaceNoSeamSettleMs
? (kCloseRaceCountedDeadlineMs + kCloseRaceAssertsSettleMs)
: kCloseRaceNoSeamSettleMs;
// Long enough that a poller is still parked when the close begins, in every
// configuration, even when the settle above burns its whole budget. The close
// is what ends the wait; this timeout only bounds the scenario if the close
// never arrives, which is itself the failure, so a long poll timeout costs
// nothing on a passing run. The flat 5 s margin is headroom above the settle
// budget, not a second guess at it.
constexpr int32_t kPollTimeoutMs = kCloseRaceSettleBudgetMs + 5000;
// A poller asks for a payload buffer, so the close races a `next_event` that has
// somewhere to copy into rather than one that can only ever return 0.
constexpr int32_t kPayloadCapacity = 4096;
int Fail(const std::string& message) {
std::fprintf(stderr, "FAILED: %s\n", message.c_str());
return 1;
}
// One iteration: `pollers` threads parked in `next_event`, then a close under
// them. Returns an empty string when every promise held.
//
// Each poller makes exactly one blocking call and does not call again. Section 5
// of the header puts the ordering on the host — no call may *begin* during a
// close — so a scenario that kept re-entering would be testing a shape the
// bridge declines to support, and its report would be the harness's own fault.
// A call already inside the library is the case the quiesce exists for, and that
// is the one run here.
std::string CloseRaceOnce(int pollers) {
void* runtime = nullptr;
const int32_t opened = cultmesh_quic_runtime_open("cultmesh-quic-native-tests", &runtime);
if (opened != 0 || runtime == nullptr)
return "cultmesh_quic_runtime_open returned " + std::to_string(opened);
// Counts threads about to enter the blocking call. The close waits for all of
// them, so the race is run with the pollers actually inside rather than with
// threads that have not started yet.
std::atomic<int> entering{0};
std::atomic<int> returned{0};
std::atomic<int> undefined_return{0};
std::atomic<int> timed_out{0};
// Set just before the close, and read by each poller as it returns. A poller
// that is already out by then was never in the race, and a scenario full of
// those passes without testing anything: this is what catches a bridge whose
// blocking crossing has stopped blocking.
std::atomic<bool> closing_started{false};
std::atomic<int> left_before_the_close{0};
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
#if defined(CULTMESH_QUIC_DEBUG_ASSERTS)
// Reset before any poller thread exists, so nothing here can race a
// `CallScope` that has already been counted: `cultmesh_quic_next_event`
// raises `debug_peak_calls` the moment its `CallScope` is constructed, at
// the very top of the call, and a reset performed even microseconds after
// that construction would zero a count no further call is coming to raise
// again, stranding the settle loop below at "not yet" until its deadline.
cultmesh_quic_debug_hold_calls(1);
cultmesh_quic_debug_hold_calls(0);
#endif
std::vector<std::thread> threads;
threads.reserve(static_cast<size_t>(pollers));
for (int index = 0; index < pollers; ++index) {
threads.emplace_back([runtime, &entering, &returned, &undefined_return, &timed_out,
&closing_started, &left_before_the_close] {
std::vector<uint8_t> payload(static_cast<size_t>(kPayloadCapacity));
cultmesh_quic_event event{};
int32_t required = 0;
entering.fetch_add(1);
const auto started = std::chrono::steady_clock::now();
// Parked here when the close arrives. `closing` wakes the wait; the
// call scope this thread holds is what the close must count out
// before it frees anything.
const int32_t result = cultmesh_quic_next_event(
runtime, kPollTimeoutMs, &event, payload.data(), kPayloadCapacity, &required);
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - started).count();
if (result != 0 && result != 1 && result != 2) undefined_return.fetch_add(1);
// The close is what ends this wait. Sitting out the whole timeout
// instead means the wake never reached it.
if (elapsed >= kPollTimeoutMs / 2) timed_out.fetch_add(1);
if (!closing_started.load()) left_before_the_close.fetch_add(1);
returned.fetch_add(1);
});
}
while (entering.load() < pollers) std::this_thread::yield();
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
#if defined(CULTMESH_QUIC_DEBUG_ASSERTS)
// A fixed settle races CPU starvation: under enough contending threads a
// poller that has only incremented `entering` can sit preempted for far
// longer than any sleep this scenario could afford, so the close would
// begin before it ever reached the blocking wait — not the race this
// scenario exists to run, just a fixture that gave up too soon. Where the
// development seam is compiled in, this reads the bridge's own count
// instead of guessing at a duration: the settle ends only once every
// poller is actually counted inside, however long the scheduler took.
Make closerace's poll timeout outlast its own settle budget Under CPU starvation (16 burners on 8 logical CPUs), all 5 rounds failed at iteration 0 or 1 with "256 of 256 pollers returned before the close began, so they were never in the race". The fixture contradicted itself: the asserts-on settle waits up to 30s (plus a 50ms remainder) for every poller to be counted inside the library, but each poller's own next_event wait used a 5s timeout, so the first pollers timed out for real while the settle was still waiting on the last ones. The race never ran; the fixture just gave up on itself. kPollTimeoutMs is now derived from the settle budget it must outlast in every build configuration (asserts-on's counted deadline plus its fixed remainder, and the no-seam #else's longer fixed window), named side by side so they cannot drift apart again. A long poll timeout costs nothing on a passing run, since the close is what actually ends each wait. Also give the fixture's own failure to settle a distinct report. If counted_deadline expires before every poller is counted, CloseRaceOnce now says "fixture could not settle: N of M pollers counted inside after 30000 ms" instead of proceeding to a close that blames the pollers for a race the fixture never set up. holdclose, latecall and holdtimeout use the same counted-deadline idiom but are not the same defect: their pollers are parked behind cultmesh_quic_debug_hold_calls, so they cannot time out for real while the settle waits, and were left alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 13:40:42 +02:00
const auto counted_deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(kCloseRaceCountedDeadlineMs);
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
while (cultmesh_quic_debug_peak_calls() < pollers &&
std::chrono::steady_clock::now() < counted_deadline)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
Make closerace's poll timeout outlast its own settle budget Under CPU starvation (16 burners on 8 logical CPUs), all 5 rounds failed at iteration 0 or 1 with "256 of 256 pollers returned before the close began, so they were never in the race". The fixture contradicted itself: the asserts-on settle waits up to 30s (plus a 50ms remainder) for every poller to be counted inside the library, but each poller's own next_event wait used a 5s timeout, so the first pollers timed out for real while the settle was still waiting on the last ones. The race never ran; the fixture just gave up on itself. kPollTimeoutMs is now derived from the settle budget it must outlast in every build configuration (asserts-on's counted deadline plus its fixed remainder, and the no-seam #else's longer fixed window), named side by side so they cannot drift apart again. A long poll timeout costs nothing on a passing run, since the close is what actually ends each wait. Also give the fixture's own failure to settle a distinct report. If counted_deadline expires before every poller is counted, CloseRaceOnce now says "fixture could not settle: N of M pollers counted inside after 30000 ms" instead of proceeding to a close that blames the pollers for a race the fixture never set up. holdclose, latecall and holdtimeout use the same counted-deadline idiom but are not the same defect: their pollers are parked behind cultmesh_quic_debug_hold_calls, so they cannot time out for real while the settle waits, and were left alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 13:40:42 +02:00
// The fixture's own settle can fail to reach every poller inside the
// deadline above, distinct from the race this scenario exists to run.
// Proceeding to the close anyway would then produce the pollers' own
// failure message — "N of M pollers returned before the close began" —
// which blames the pollers for a race the fixture never set up. Report
// this plainly instead, and unstick the parked pollers before returning so
// this iteration does not just sit out `kPollTimeoutMs`.
const int32_t counted = cultmesh_quic_debug_peak_calls();
if (counted < pollers) {
closing_started.store(true);
cultmesh_quic_runtime_close(runtime);
for (auto& thread : threads) thread.join();
return "fixture could not settle: " + std::to_string(counted) + " of " +
std::to_string(pollers) + " pollers counted inside after " +
std::to_string(kCloseRaceCountedDeadlineMs) + " ms";
}
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
// `active_calls` — what `peak_calls` reflects — is raised the moment a
// poller's `CallScope` is constructed, at the top of
// `cultmesh_quic_next_event`, not when it reaches the condition wait a few
// statements later. Counted-inside is the expensive part to wait for under
// starvation (getting the scheduler to run a thread at all); a short fixed
// settle covers the cheap remainder (a lock acquisition and an empty-queue
// check) once every poller is already scheduled and running.
Make closerace's poll timeout outlast its own settle budget Under CPU starvation (16 burners on 8 logical CPUs), all 5 rounds failed at iteration 0 or 1 with "256 of 256 pollers returned before the close began, so they were never in the race". The fixture contradicted itself: the asserts-on settle waits up to 30s (plus a 50ms remainder) for every poller to be counted inside the library, but each poller's own next_event wait used a 5s timeout, so the first pollers timed out for real while the settle was still waiting on the last ones. The race never ran; the fixture just gave up on itself. kPollTimeoutMs is now derived from the settle budget it must outlast in every build configuration (asserts-on's counted deadline plus its fixed remainder, and the no-seam #else's longer fixed window), named side by side so they cannot drift apart again. A long poll timeout costs nothing on a passing run, since the close is what actually ends each wait. Also give the fixture's own failure to settle a distinct report. If counted_deadline expires before every poller is counted, CloseRaceOnce now says "fixture could not settle: N of M pollers counted inside after 30000 ms" instead of proceeding to a close that blames the pollers for a race the fixture never set up. holdclose, latecall and holdtimeout use the same counted-deadline idiom but are not the same defect: their pollers are parked behind cultmesh_quic_debug_hold_calls, so they cannot time out for real while the settle waits, and were left alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 13:40:42 +02:00
std::this_thread::sleep_for(std::chrono::milliseconds(kCloseRaceAssertsSettleMs));
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
#else
// No seam in this configuration (the sanitizer build deliberately ships
// without one), so the settle falls back to a longer fixed window rather
// than the 50 ms that starved under 16 contending burners. It is still a
// window, not a guarantee — the honest limit of a config with no way to
// ask the bridge what it has actually counted.
Make closerace's poll timeout outlast its own settle budget Under CPU starvation (16 burners on 8 logical CPUs), all 5 rounds failed at iteration 0 or 1 with "256 of 256 pollers returned before the close began, so they were never in the race". The fixture contradicted itself: the asserts-on settle waits up to 30s (plus a 50ms remainder) for every poller to be counted inside the library, but each poller's own next_event wait used a 5s timeout, so the first pollers timed out for real while the settle was still waiting on the last ones. The race never ran; the fixture just gave up on itself. kPollTimeoutMs is now derived from the settle budget it must outlast in every build configuration (asserts-on's counted deadline plus its fixed remainder, and the no-seam #else's longer fixed window), named side by side so they cannot drift apart again. A long poll timeout costs nothing on a passing run, since the close is what actually ends each wait. Also give the fixture's own failure to settle a distinct report. If counted_deadline expires before every poller is counted, CloseRaceOnce now says "fixture could not settle: N of M pollers counted inside after 30000 ms" instead of proceeding to a close that blames the pollers for a race the fixture never set up. holdclose, latecall and holdtimeout use the same counted-deadline idiom but are not the same defect: their pollers are parked behind cultmesh_quic_debug_hold_calls, so they cannot time out for real while the settle waits, and were left alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 13:40:42 +02:00
std::this_thread::sleep_for(std::chrono::milliseconds(kCloseRaceNoSeamSettleMs));
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
#endif
closing_started.store(true);
cultmesh_quic_runtime_close(runtime);
for (auto& thread : threads) thread.join();
if (left_before_the_close.load() != 0)
return std::to_string(left_before_the_close.load()) + " of " + std::to_string(pollers) +
" pollers returned before the close began, so they were never in the race";
if (undefined_return.load() != 0)
return std::to_string(undefined_return.load()) + " poller(s) saw a return the header does not define";
if (timed_out.load() != 0)
return std::to_string(timed_out.load()) + " of " + std::to_string(pollers) +
" pollers sat out the timeout instead of being woken by the close";
if (returned.load() != pollers)
return std::to_string(returned.load()) + " of " + std::to_string(pollers) + " pollers returned";
return {};
}
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
// The three timeouts `polltimeout` asks for: a spread, not a hunt for any
// particular derivation. `waitseam` is what proves the bridge hands its
// condition wait the host's own argument, exactly, at every value that matters
// — these three exist only to prove that value is then really waited on, so
// the spread just needs to be a short one, an ordinary one and a long one.
Probe the host's timeout at 15, 200 and 7300 ms, and stress the tolerance Soul's sixth pass (N2): with probes at 150, 200 and 5000 ms, `min(t,5000)`, `min(t,4990)`, `t*11/10` and `t+70` survived both targets, `max(t,220)` survived linux-x64, and `max(t,250)` and `t+80` died by 22 and 2 ms. "5000 is above any plausible ceiling" was wrong: 5000 is a round ceiling. - A 15 ms probe, asked ten times with the fastest checked against 30 ms. It is the only place a floor or an added constant shows. Scheduler delay only adds, so noise has to delay all ten attempts to fail the bridge. - The long probe is 7300 ms, which is not a round number. - A single poll gets 60 ms plus a sixteenth of the timeout. - Each timed scenario prints its worst overshoot per probe. - Three harness entries: a round clamp `min(t,5000)`, a floor `max(t,100)` and an offset `t+100`. Stressed with two busy threads per logical CPU (16 on 8), five rounds of every timed scenario on both targets. At normal priority, Windows put a single poll up to 127 ms late and the fastest of ten 15 ms attempts up to 87 ms late, and a 45 ms tolerance failed the unmutated bridge in four rounds of five. The timed scenarios therefore raise their own process to HIGH_PRIORITY_CLASS on win32-x64, which needs no privilege. With that, the worst single-poll overshoot was 15 ms on win32-x64 and 41 ms on linux-x64, at normal priority there. The fastest 15 ms attempt was 0 ms late on both. The limit in source is measured, not asserted. Each boundary mutant was compiled in and run on both targets: - `t+25` survived and `t+40` died; - `max(t,40)` survived and `max(t,50)` died, on linux-x64 by 5 ms; - `t*106/100` survived and `t*108/100` died; - `min(t,7285)` survived and `min(t,7275)` died. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:59:37 +02:00
constexpr int32_t kShortestPollMs = 15;
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
constexpr int32_t kShortPollMs = 200;
Probe the host's timeout at 15, 200 and 7300 ms, and stress the tolerance Soul's sixth pass (N2): with probes at 150, 200 and 5000 ms, `min(t,5000)`, `min(t,4990)`, `t*11/10` and `t+70` survived both targets, `max(t,220)` survived linux-x64, and `max(t,250)` and `t+80` died by 22 and 2 ms. "5000 is above any plausible ceiling" was wrong: 5000 is a round ceiling. - A 15 ms probe, asked ten times with the fastest checked against 30 ms. It is the only place a floor or an added constant shows. Scheduler delay only adds, so noise has to delay all ten attempts to fail the bridge. - The long probe is 7300 ms, which is not a round number. - A single poll gets 60 ms plus a sixteenth of the timeout. - Each timed scenario prints its worst overshoot per probe. - Three harness entries: a round clamp `min(t,5000)`, a floor `max(t,100)` and an offset `t+100`. Stressed with two busy threads per logical CPU (16 on 8), five rounds of every timed scenario on both targets. At normal priority, Windows put a single poll up to 127 ms late and the fastest of ten 15 ms attempts up to 87 ms late, and a 45 ms tolerance failed the unmutated bridge in four rounds of five. The timed scenarios therefore raise their own process to HIGH_PRIORITY_CLASS on win32-x64, which needs no privilege. With that, the worst single-poll overshoot was 15 ms on win32-x64 and 41 ms on linux-x64, at normal priority there. The fastest 15 ms attempt was 0 ms late on both. The limit in source is measured, not asserted. Each boundary mutant was compiled in and run on both targets: - `t+25` survived and `t+40` died; - `max(t,40)` survived and `max(t,50)` died, on linux-x64 by 5 ms; - `t*106/100` survived and `t*108/100` died; - `min(t,7285)` survived and `min(t,7275)` died. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:59:37 +02:00
constexpr int32_t kLongPollMs = 7300;
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
// How much sooner than the timeout a poll may return. A wait may be late; it may
// not be early, because returning early is exactly what waiting on a shorter
// duration looks like. This is scheduler granularity, not slack.
constexpr int kEarlyToleranceMs = 20;
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
// How much later than the timeout a poll may return. Generous on purpose: these
// scenarios are no longer where a clamp, a floor, a round or a scale is caught
// — `waitseam` catches those by equality, with no clock involved — so this only
// has to catch a wait that is wrong by a lot: a constant of the bridge's own, a
// wait that never happens, a hold that parks a call its own timeout already
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
// ended, or the W1-W3 family Self's ruling of 2026-09-22 added, which apply
// their arithmetic to the duration `wait_for` actually receives *after* the
// seam has already recorded it and so cannot be caught by `waitseam`'s
// equality check at all. 400 ms sits above the worst overshoot a 16-burner
// stress pass measured on a loaded win32 host (180 ms; 27 ms on linux) and
// below every one of W1-W3's smallest offsets (485 ms), so one flat margin
// still serves every probe this file times without going blind to the family
// it was widened for.
constexpr int kGenerousLateToleranceMs = 400;
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
struct TimedPoll {
int32_t result;
long long elapsed_ms;
};
TimedPoll PollFor(void* runtime, int32_t timeout_ms) {
std::vector<uint8_t> payload(static_cast<size_t>(kPayloadCapacity));
cultmesh_quic_event event{};
int32_t required = 0;
const auto started = std::chrono::steady_clock::now();
const int32_t result = cultmesh_quic_next_event(
runtime, timeout_ms, &event, payload.data(), kPayloadCapacity, &required);
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - started).count();
return {result, elapsed};
}
// The host's timeout, and the only thing it is: how long a poll with nothing to
// deliver stays inside the library. Nothing else here checks it — `closerace`
// and `holdclose` both end their waits with a close, so in both of them the
// bridge could wait on any duration at all and every assertion would still hold.
// A runtime with no listener and no connection has nothing to deliver, so the
// timeout is what ends this poll, and the elapsed time is what it waited on.
Probe the host's timeout at 15, 200 and 7300 ms, and stress the tolerance Soul's sixth pass (N2): with probes at 150, 200 and 5000 ms, `min(t,5000)`, `min(t,4990)`, `t*11/10` and `t+70` survived both targets, `max(t,220)` survived linux-x64, and `max(t,250)` and `t+80` died by 22 and 2 ms. "5000 is above any plausible ceiling" was wrong: 5000 is a round ceiling. - A 15 ms probe, asked ten times with the fastest checked against 30 ms. It is the only place a floor or an added constant shows. Scheduler delay only adds, so noise has to delay all ten attempts to fail the bridge. - The long probe is 7300 ms, which is not a round number. - A single poll gets 60 ms plus a sixteenth of the timeout. - Each timed scenario prints its worst overshoot per probe. - Three harness entries: a round clamp `min(t,5000)`, a floor `max(t,100)` and an offset `t+100`. Stressed with two busy threads per logical CPU (16 on 8), five rounds of every timed scenario on both targets. At normal priority, Windows put a single poll up to 127 ms late and the fastest of ten 15 ms attempts up to 87 ms late, and a 45 ms tolerance failed the unmutated bridge in four rounds of five. The timed scenarios therefore raise their own process to HIGH_PRIORITY_CLASS on win32-x64, which needs no privilege. With that, the worst single-poll overshoot was 15 ms on win32-x64 and 41 ms on linux-x64, at normal priority there. The fastest 15 ms attempt was 0 ms late on both. The limit in source is measured, not asserted. Each boundary mutant was compiled in and run on both targets: - `t+25` survived and `t+40` died; - `max(t,40)` survived and `max(t,50)` died, on linux-x64 by 5 ms; - `t*106/100` survived and `t*108/100` died; - `min(t,7285)` survived and `min(t,7275)` died. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:59:37 +02:00
// The bands every timed poll is held to, and the worst overshoot seen, which is
// printed so a tolerance can be checked against a machine under load rather than
// trusted.
struct Overshoot {
long long worst_ms = LLONG_MIN;
void Record(const TimedPoll& poll, int32_t timeout_ms) {
worst_ms = (std::max)(worst_ms, poll.elapsed_ms - timeout_ms);
}
};
std::string CheckTimedPoll(const TimedPoll& poll, int32_t timeout_ms, int late_tolerance_ms) {
const std::string asked = "a poll asking for " + std::to_string(timeout_ms) + " ms ";
if (poll.result != 0)
return asked + "returned " + std::to_string(poll.result) + ", not the 0 an idle runtime owes it";
if (poll.elapsed_ms < timeout_ms - kEarlyToleranceMs)
return asked + "returned after " + std::to_string(poll.elapsed_ms) +
" ms: the wait ended on some shorter duration than the one it was given";
if (poll.elapsed_ms > static_cast<long long>(timeout_ms) + late_tolerance_ms)
return asked + "returned after " + std::to_string(poll.elapsed_ms) +
" ms: the wait ended on some longer duration than the one it was given";
return {};
}
constexpr std::array<int32_t, 3> kTimedProbesMs{kShortestPollMs, kShortPollMs, kLongPollMs};
std::string PollTimeoutOnce(std::array<Overshoot, 3>& overshoot) {
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
void* runtime = nullptr;
const int32_t opened = cultmesh_quic_runtime_open("cultmesh-quic-native-tests", &runtime);
if (opened != 0 || runtime == nullptr)
return "cultmesh_quic_runtime_open returned " + std::to_string(opened);
std::string failure;
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
for (size_t probe = 0; probe < kTimedProbesMs.size() && failure.empty(); ++probe) {
Probe the host's timeout at 15, 200 and 7300 ms, and stress the tolerance Soul's sixth pass (N2): with probes at 150, 200 and 5000 ms, `min(t,5000)`, `min(t,4990)`, `t*11/10` and `t+70` survived both targets, `max(t,220)` survived linux-x64, and `max(t,250)` and `t+80` died by 22 and 2 ms. "5000 is above any plausible ceiling" was wrong: 5000 is a round ceiling. - A 15 ms probe, asked ten times with the fastest checked against 30 ms. It is the only place a floor or an added constant shows. Scheduler delay only adds, so noise has to delay all ten attempts to fail the bridge. - The long probe is 7300 ms, which is not a round number. - A single poll gets 60 ms plus a sixteenth of the timeout. - Each timed scenario prints its worst overshoot per probe. - Three harness entries: a round clamp `min(t,5000)`, a floor `max(t,100)` and an offset `t+100`. Stressed with two busy threads per logical CPU (16 on 8), five rounds of every timed scenario on both targets. At normal priority, Windows put a single poll up to 127 ms late and the fastest of ten 15 ms attempts up to 87 ms late, and a 45 ms tolerance failed the unmutated bridge in four rounds of five. The timed scenarios therefore raise their own process to HIGH_PRIORITY_CLASS on win32-x64, which needs no privilege. With that, the worst single-poll overshoot was 15 ms on win32-x64 and 41 ms on linux-x64, at normal priority there. The fastest 15 ms attempt was 0 ms late on both. The limit in source is measured, not asserted. Each boundary mutant was compiled in and run on both targets: - `t+25` survived and `t+40` died; - `max(t,40)` survived and `max(t,50)` died, on linux-x64 by 5 ms; - `t*106/100` survived and `t*108/100` died; - `min(t,7285)` survived and `min(t,7275)` died. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:59:37 +02:00
const TimedPoll poll = PollFor(runtime, kTimedProbesMs[probe]);
overshoot[probe].Record(poll, kTimedProbesMs[probe]);
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
failure = CheckTimedPoll(poll, kTimedProbesMs[probe], kGenerousLateToleranceMs);
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
}
cultmesh_quic_runtime_close(runtime);
return failure;
}
int PollTimeout(int iterations) {
Probe the host's timeout at 15, 200 and 7300 ms, and stress the tolerance Soul's sixth pass (N2): with probes at 150, 200 and 5000 ms, `min(t,5000)`, `min(t,4990)`, `t*11/10` and `t+70` survived both targets, `max(t,220)` survived linux-x64, and `max(t,250)` and `t+80` died by 22 and 2 ms. "5000 is above any plausible ceiling" was wrong: 5000 is a round ceiling. - A 15 ms probe, asked ten times with the fastest checked against 30 ms. It is the only place a floor or an added constant shows. Scheduler delay only adds, so noise has to delay all ten attempts to fail the bridge. - The long probe is 7300 ms, which is not a round number. - A single poll gets 60 ms plus a sixteenth of the timeout. - Each timed scenario prints its worst overshoot per probe. - Three harness entries: a round clamp `min(t,5000)`, a floor `max(t,100)` and an offset `t+100`. Stressed with two busy threads per logical CPU (16 on 8), five rounds of every timed scenario on both targets. At normal priority, Windows put a single poll up to 127 ms late and the fastest of ten 15 ms attempts up to 87 ms late, and a 45 ms tolerance failed the unmutated bridge in four rounds of five. The timed scenarios therefore raise their own process to HIGH_PRIORITY_CLASS on win32-x64, which needs no privilege. With that, the worst single-poll overshoot was 15 ms on win32-x64 and 41 ms on linux-x64, at normal priority there. The fastest 15 ms attempt was 0 ms late on both. The limit in source is measured, not asserted. Each boundary mutant was compiled in and run on both targets: - `t+25` survived and `t+40` died; - `max(t,40)` survived and `max(t,50)` died, on linux-x64 by 5 ms; - `t*106/100` survived and `t*108/100` died; - `min(t,7285)` survived and `min(t,7275)` died. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:59:37 +02:00
std::array<Overshoot, 3> overshoot{};
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
for (int iteration = 0; iteration < iterations; ++iteration) {
Probe the host's timeout at 15, 200 and 7300 ms, and stress the tolerance Soul's sixth pass (N2): with probes at 150, 200 and 5000 ms, `min(t,5000)`, `min(t,4990)`, `t*11/10` and `t+70` survived both targets, `max(t,220)` survived linux-x64, and `max(t,250)` and `t+80` died by 22 and 2 ms. "5000 is above any plausible ceiling" was wrong: 5000 is a round ceiling. - A 15 ms probe, asked ten times with the fastest checked against 30 ms. It is the only place a floor or an added constant shows. Scheduler delay only adds, so noise has to delay all ten attempts to fail the bridge. - The long probe is 7300 ms, which is not a round number. - A single poll gets 60 ms plus a sixteenth of the timeout. - Each timed scenario prints its worst overshoot per probe. - Three harness entries: a round clamp `min(t,5000)`, a floor `max(t,100)` and an offset `t+100`. Stressed with two busy threads per logical CPU (16 on 8), five rounds of every timed scenario on both targets. At normal priority, Windows put a single poll up to 127 ms late and the fastest of ten 15 ms attempts up to 87 ms late, and a 45 ms tolerance failed the unmutated bridge in four rounds of five. The timed scenarios therefore raise their own process to HIGH_PRIORITY_CLASS on win32-x64, which needs no privilege. With that, the worst single-poll overshoot was 15 ms on win32-x64 and 41 ms on linux-x64, at normal priority there. The fastest 15 ms attempt was 0 ms late on both. The limit in source is measured, not asserted. Each boundary mutant was compiled in and run on both targets: - `t+25` survived and `t+40` died; - `max(t,40)` survived and `max(t,50)` died, on linux-x64 by 5 ms; - `t*106/100` survived and `t*108/100` died; - `min(t,7285)` survived and `min(t,7275)` died. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:59:37 +02:00
const std::string failure = PollTimeoutOnce(overshoot);
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
if (!failure.empty())
return Fail("polltimeout iteration " + std::to_string(iteration) + ": " + failure);
}
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
std::printf("polltimeout %dx: ok (worst overshoot %lld ms at %d, %lld ms at %d, %lld ms at %d)\n",
iterations, overshoot[0].worst_ms, kTimedProbesMs[0], overshoot[1].worst_ms, kTimedProbesMs[1],
Probe the host's timeout at 15, 200 and 7300 ms, and stress the tolerance Soul's sixth pass (N2): with probes at 150, 200 and 5000 ms, `min(t,5000)`, `min(t,4990)`, `t*11/10` and `t+70` survived both targets, `max(t,220)` survived linux-x64, and `max(t,250)` and `t+80` died by 22 and 2 ms. "5000 is above any plausible ceiling" was wrong: 5000 is a round ceiling. - A 15 ms probe, asked ten times with the fastest checked against 30 ms. It is the only place a floor or an added constant shows. Scheduler delay only adds, so noise has to delay all ten attempts to fail the bridge. - The long probe is 7300 ms, which is not a round number. - A single poll gets 60 ms plus a sixteenth of the timeout. - Each timed scenario prints its worst overshoot per probe. - Three harness entries: a round clamp `min(t,5000)`, a floor `max(t,100)` and an offset `t+100`. Stressed with two busy threads per logical CPU (16 on 8), five rounds of every timed scenario on both targets. At normal priority, Windows put a single poll up to 127 ms late and the fastest of ten 15 ms attempts up to 87 ms late, and a 45 ms tolerance failed the unmutated bridge in four rounds of five. The timed scenarios therefore raise their own process to HIGH_PRIORITY_CLASS on win32-x64, which needs no privilege. With that, the worst single-poll overshoot was 15 ms on win32-x64 and 41 ms on linux-x64, at normal priority there. The fastest 15 ms attempt was 0 ms late on both. The limit in source is measured, not asserted. Each boundary mutant was compiled in and run on both targets: - `t+25` survived and `t+40` died; - `max(t,40)` survived and `max(t,50)` died, on linux-x64 by 5 ms; - `t*106/100` survived and `t*108/100` died; - `min(t,7285)` survived and `min(t,7275)` died. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:59:37 +02:00
overshoot[2].worst_ms, kTimedProbesMs[2]);
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
return 0;
}
// How long `pollbusy`'s poll asks for, and how often the host's other thread
// calls into the library while it waits.
constexpr int32_t kBusyPollMs = 1000;
constexpr int kBusyCallEveryMs = 20;
// The same rule as `polltimeout` — a poll with nothing to deliver stays for its
// timeout — for a host with more than one thread, which is every real host: one
// thread polls, another sends, shuts streams down, reads the last error.
//
// Every host call leaves through a scope that wakes every waiter on the
// runtime's condition variable, because that is how the close learns a call has
// left. So a poll is woken each time any other thread makes any call, and what
// keeps it inside is the wait's predicate: nothing queued and no close means
// wait again, for the rest of the time the host asked for. With one host thread
// nothing else ever calls during the wait, and the predicate could be deleted
// with every other scenario still green.
//
// The other thread keeps calling for twice the poll's timeout and then stops,
// so a wait that restarts its whole timeout on every wake is seen as late
// rather than hanging the scenario.
std::string PollBusyOnce(Overshoot& overshoot) {
void* runtime = nullptr;
const int32_t opened = cultmesh_quic_runtime_open("cultmesh-quic-native-tests", &runtime);
if (opened != 0 || runtime == nullptr)
return "cultmesh_quic_runtime_open returned " + std::to_string(opened);
std::atomic<int> calls{0};
std::atomic<int> refused{0};
std::thread other([runtime, &calls, &refused] {
const auto until = std::chrono::steady_clock::now() + std::chrono::milliseconds(2 * kBusyPollMs);
std::array<char, 256> message{};
while (std::chrono::steady_clock::now() < until) {
if (cultmesh_quic_last_error(runtime, message.data(), static_cast<int32_t>(message.size())) < 0)
refused.fetch_add(1);
calls.fetch_add(1);
std::this_thread::sleep_for(std::chrono::milliseconds(kBusyCallEveryMs));
}
});
const TimedPoll poll = PollFor(runtime, kBusyPollMs);
const int calls_during = calls.load();
other.join();
cultmesh_quic_runtime_close(runtime);
overshoot.Record(poll, kBusyPollMs);
if (refused.load() != 0)
return std::to_string(refused.load()) + " call(s) from the host's other thread were refused "
"by a runtime that was not closing";
// The timing first: a poll that left on the first wake returns before the
// other thread has had time to make many calls, and that is the bridge
// failing, not the fixture.
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
const std::string failure = CheckTimedPoll(poll, kBusyPollMs, kGenerousLateToleranceMs);
if (!failure.empty()) return failure + ", while the host's other thread was calling in";
// A poll that passed with fewer calls than this was never woken, and passed
// because the fixture did nothing rather than because the bridge held.
if (calls_during < 3)
return "the host's other thread made " + std::to_string(calls_during) +
" call(s) during the poll, so nothing woke it";
return {};
}
int PollBusy(int iterations) {
Overshoot overshoot{};
for (int iteration = 0; iteration < iterations; ++iteration) {
const std::string failure = PollBusyOnce(overshoot);
if (!failure.empty())
return Fail("pollbusy iteration " + std::to_string(iteration) + ": " + failure);
}
std::printf("pollbusy %dx: ok (worst overshoot %lld ms at %d)\n", iterations, overshoot.worst_ms,
kBusyPollMs);
return 0;
}
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
// Nothing listens on the discard port, so a connection to it fails and the
// bridge says why. That reason is the payload this scenario needs, and reaching
// it takes no listener, no credential and no established connection.
constexpr uint16_t kClosedPort = 9;
// How long the failure is given to arrive. The loopback refusal is immediate;
// if the refusal is swallowed instead, MsQuic's own handshake timeout ends the
// connection at 10 seconds and event 4 still arrives, so this only bounds a
// bridge that reports nothing at all.
constexpr int kEventDeadlineMs = 20000;
// The bridge writes its own reason sentences and every one of them starts here.
// Checking the bytes, and not only the length, is what separates a payload
// copied out of the queued event from one copied out of an event that was popped
// first: the second reads freed memory, which does not spell this.
constexpr char kReasonPrefix[] = "CultMesh QUIC connection";
// The two-phase poll, which is the shape every host uses to size its buffer:
// ask with nothing, be told what it needs, ask again. What makes it work is that
// the refusal consumes nothing — the event is still queued, payload and all,
// after a return of 2 — and that the copy happens before the event is popped.
// Both are section 6 of the header, and nothing committed reached either.
std::string PayloadFitOnce() {
void* runtime = nullptr;
const int32_t opened = cultmesh_quic_runtime_open("cultmesh-quic-native-tests", &runtime);
if (opened != 0 || runtime == nullptr)
return "cultmesh_quic_runtime_open returned " + std::to_string(opened);
std::string failure;
uint64_t connection_id = 0;
const int32_t connecting =
cultmesh_quic_connection_open(runtime, "127.0.0.1", kClosedPort, &connection_id);
if (connecting != 0) {
cultmesh_quic_runtime_close(runtime);
return "cultmesh_quic_connection_open returned " + std::to_string(connecting);
}
// An out parameter the bridge must not write on a refusal, filled with
// something the bridge never writes so that a write is visible.
cultmesh_quic_event poisoned{};
std::memset(&poisoned, 0xab, sizeof(poisoned));
cultmesh_quic_event event = poisoned;
int32_t required = 0;
int32_t result = 0;
// Ask with no room at all until the event carrying the failure reason
// arrives. An event with no payload fits in nothing, so it is delivered and
// polled past rather than mistaken for this one.
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(kEventDeadlineMs);
for (;;) {
event = poisoned;
required = 0;
result = cultmesh_quic_next_event(runtime, 250, &event, nullptr, 0, &required);
if (result == 2 && required > 0) break;
if (result < 0) {
failure = "cultmesh_quic_next_event returned " + std::to_string(result);
break;
}
if (std::chrono::steady_clock::now() >= deadline) {
failure = "no event carrying a payload arrived within " +
std::to_string(kEventDeadlineMs) + " ms of a connection to a closed port";
break;
}
}
if (failure.empty() && std::memcmp(&event, &poisoned, sizeof(event)) != 0)
failure = "the refusal wrote to *out_event, which the header says it leaves alone";
// Again, with the same empty buffer: a refusal that consumed the event
// leaves nothing to find the second time.
if (failure.empty()) {
cultmesh_quic_event again = poisoned;
int32_t required_again = 0;
const int32_t second = cultmesh_quic_next_event(runtime, 0, &again, nullptr, 0, &required_again);
if (second != 2 || required_again != required)
failure = "the second ask returned " + std::to_string(second) + " needing " +
std::to_string(required_again) + ", not the 2 needing " + std::to_string(required) +
" the first one did: the refusal consumed the event";
}
// And with room, which is where the copy happens. The tail beyond the
// payload is written too, so a copy that ran past what it was told to write
// is visible.
if (failure.empty()) {
constexpr uint8_t kTail = 0x5a;
std::vector<uint8_t> payload(static_cast<size_t>(required) + 16, kTail);
cultmesh_quic_event delivered = poisoned;
int32_t required_delivered = 0;
const int32_t third = cultmesh_quic_next_event(
runtime, 0, &delivered, payload.data(), static_cast<int32_t>(payload.size()),
&required_delivered);
const std::string reason(reinterpret_cast<const char*>(payload.data()),
static_cast<size_t>(required));
if (third != 1 || required_delivered != required)
failure = "the ask with room returned " + std::to_string(third) + " needing " +
std::to_string(required_delivered) + ", not the 1 needing " +
std::to_string(required) + " the refusal promised";
else if (delivered.type != CULTMESH_QUIC_EVENT_CONNECTION_SHUTDOWN ||
delivered.connection_id != connection_id ||
delivered.payload_length != required)
failure = "the delivered event was type " + std::to_string(delivered.type) +
" on connection " + std::to_string(delivered.connection_id) + " carrying " +
std::to_string(delivered.payload_length) + " byte(s), not the failed connection's own";
else if (reason.rfind(kReasonPrefix, 0) != 0)
failure = "the payload delivered was '" + reason + "', not a reason this bridge writes";
else if (payload[static_cast<size_t>(required)] != kTail)
failure = "the copy wrote past the " + std::to_string(required) + " bytes it reported";
}
// Consumed now, and only now.
if (failure.empty()) {
cultmesh_quic_event emptied = poisoned;
int32_t required_emptied = 0;
const int32_t fourth =
cultmesh_quic_next_event(runtime, 0, &emptied, nullptr, 0, &required_emptied);
if (fourth != 0)
failure = "the event was still queued after being delivered: a further ask returned " +
std::to_string(fourth);
}
cultmesh_quic_runtime_close(runtime);
return failure;
}
int PayloadFit(int iterations) {
for (int iteration = 0; iteration < iterations; ++iteration) {
const std::string failure = PayloadFitOnce();
if (!failure.empty())
return Fail("payloadfit iteration " + std::to_string(iteration) + ": " + failure);
}
std::printf("payloadfit %dx: ok\n", iterations);
return 0;
}
#if defined(CULTMESH_QUIC_DEBUG_ASSERTS)
// How long a poll that does not block is given to prove it by returning, after
// the calls have been counted and before the close begins. A bridge whose
// blocking wait no longer blocks has emptied itself of host calls by the time
// the close starts, and `calls_at_close` then says 0.
constexpr int kSettleMs = 100;
// How long the close is required to stay inside `cultmesh_quic_runtime_close`
// while the pollers are held. The held calls are what it is waiting for, and
// nothing releases them until this elapses, so a close that returns first gave
// up on a call that was still inside. This bounds what the scenario can see: a
// close that waits for longer than this and then gives up is not distinguished
// from one that waits properly, which is the honest limit of the check.
constexpr int kHeldWindowMs = 500;
// How long the pollers are given to be counted inside the library before the
// close begins. Reaching `pollers` is the normal path; the deadline only stops
// a bridge that never counts them from hanging the scenario.
constexpr int kCountedDeadlineMs = 2000;
// One iteration of the quiesce itself, with the guesswork taken out: the pollers
// are held inside the library by the bridge's own development seam until this
// scenario lets them go, and the bridge says how many calls it counted rather
// than the scenario assuming the schedule put them there.
//
// Three promises, and a mutation that breaks any one of them is seen here:
// - the calls are counted (`peak`), so a blocking crossing outside a call scope
// is not merely uncounted-but-lucky, it reads as zero;
// - they were still counted when the close began its wait (`calls_at_close`),
// so a poll that stopped blocking cannot leave the scenario testing an empty
// library;
// - the close did not return while they were held, so a wait that gives up
// after any interval shorter than the hold is not a wait.
std::string HoldCloseOnce(int pollers) {
cultmesh_quic_debug_hold_calls(1);
void* runtime = nullptr;
const int32_t opened = cultmesh_quic_runtime_open("cultmesh-quic-native-tests", &runtime);
if (opened != 0 || runtime == nullptr) {
cultmesh_quic_debug_hold_calls(0);
return "cultmesh_quic_runtime_open returned " + std::to_string(opened);
}
std::atomic<int> returned{0};
std::atomic<int> undefined_return{0};
std::vector<std::thread> threads;
threads.reserve(static_cast<size_t>(pollers));
for (int index = 0; index < pollers; ++index) {
threads.emplace_back([runtime, &returned, &undefined_return] {
std::vector<uint8_t> payload(static_cast<size_t>(kPayloadCapacity));
cultmesh_quic_event event{};
int32_t required = 0;
const int32_t result = cultmesh_quic_next_event(
runtime, kPollTimeoutMs, &event, payload.data(), kPayloadCapacity, &required);
if (result != 0 && result != 1 && result != 2) undefined_return.fetch_add(1);
returned.fetch_add(1);
});
}
const auto counted_deadline =
std::chrono::steady_clock::now() + std::chrono::milliseconds(kCountedDeadlineMs);
while (cultmesh_quic_debug_peak_calls() < pollers &&
std::chrono::steady_clock::now() < counted_deadline)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::this_thread::sleep_for(std::chrono::milliseconds(kSettleMs));
std::atomic<bool> closed{false};
std::thread closer([runtime, &closed] {
cultmesh_quic_runtime_close(runtime);
closed.store(true);
});
const auto held_until =
std::chrono::steady_clock::now() + std::chrono::milliseconds(kHeldWindowMs);
while (!closed.load() && std::chrono::steady_clock::now() < held_until)
std::this_thread::sleep_for(std::chrono::milliseconds(5));
const bool closed_while_held = closed.load();
const int32_t peak = cultmesh_quic_debug_peak_calls();
const int32_t at_close = cultmesh_quic_debug_calls_at_close();
cultmesh_quic_debug_hold_calls(0);
closer.join();
for (auto& thread : threads) thread.join();
if (peak != pollers)
return "the library's peak count of host calls inside it was " + std::to_string(peak) + ", not " +
std::to_string(pollers) + ": either the blocking crossing is not inside a call scope, or it " +
"did not block long enough for the pollers to be inside it together";
if (at_close != pollers)
return "the close began its wait with " + std::to_string(at_close) +
" host call(s) counted inside, not " + std::to_string(pollers) +
"; the pollers were not inside the library when the close started";
if (closed_while_held)
return "the close returned while " + std::to_string(pollers) +
" host call(s) were still held inside the library";
if (undefined_return.load() != 0)
return std::to_string(undefined_return.load()) + " poller(s) saw a return the header does not define";
if (returned.load() != pollers)
return std::to_string(returned.load()) + " of " + std::to_string(pollers) + " pollers returned";
return {};
}
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
// One iteration of the other half of section 4: a call made while a close is
// running is refused, and is not counted behind the close's wait.
//
// The prohibition is the host's — no call may begin after the close starts — and
// the bridge does not make it optional. What it does do is refuse the call that
// races the start, and that refusal is load-bearing rather than courteous: a
// call admitted after the closer has begun increments the in-flight count behind
// a wait that has already read it, and the wait can then be left waiting on a
// count that reaches zero only when the late caller happens to leave.
//
// The seam is what makes the window wide enough to aim at: a held poller keeps
// the close inside its wait for as long as this scenario wants it there.
std::string LateCallOnce() {
cultmesh_quic_debug_hold_calls(1);
void* runtime = nullptr;
const int32_t opened = cultmesh_quic_runtime_open("cultmesh-quic-native-tests", &runtime);
if (opened != 0 || runtime == nullptr) {
cultmesh_quic_debug_hold_calls(0);
return "cultmesh_quic_runtime_open returned " + std::to_string(opened);
}
std::thread poller([runtime] {
std::vector<uint8_t> payload(static_cast<size_t>(kPayloadCapacity));
cultmesh_quic_event event{};
int32_t required = 0;
cultmesh_quic_next_event(
runtime, kPollTimeoutMs, &event, payload.data(), kPayloadCapacity, &required);
});
const auto counted_deadline =
std::chrono::steady_clock::now() + std::chrono::milliseconds(kCountedDeadlineMs);
while (cultmesh_quic_debug_peak_calls() < 1 &&
std::chrono::steady_clock::now() < counted_deadline)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::this_thread::sleep_for(std::chrono::milliseconds(kSettleMs));
std::thread closer([runtime] { cultmesh_quic_runtime_close(runtime); });
// The close has begun its wait once it has read the count, and the held
// poller is what keeps it there.
const auto started_deadline =
std::chrono::steady_clock::now() + std::chrono::milliseconds(kCountedDeadlineMs);
while (cultmesh_quic_debug_calls_at_close() < 0 &&
std::chrono::steady_clock::now() < started_deadline)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
const int32_t at_close = cultmesh_quic_debug_calls_at_close();
// Two calls, from a thread of their own so that a bridge which admits them
// cannot park this one: one that does not block, and then the blocking
// crossing. That order, because a bridge that admits the blocking one parks
// it inside the library until the hold is released, and a call made after
// that would be racing the teardown rather than the wait.
std::atomic<int32_t> late_poll{0};
std::atomic<int32_t> late_error{0};
std::thread late([runtime, &late_poll, &late_error] {
std::array<char, 256> message{};
late_error.store(cultmesh_quic_last_error(runtime, message.data(),
static_cast<int32_t>(message.size())));
std::vector<uint8_t> payload(static_cast<size_t>(kPayloadCapacity));
cultmesh_quic_event event{};
int32_t required = 0;
late_poll.store(cultmesh_quic_next_event(
runtime, kPollTimeoutMs, &event, payload.data(), kPayloadCapacity, &required));
});
std::this_thread::sleep_for(std::chrono::milliseconds(kSettleMs));
cultmesh_quic_debug_hold_calls(0);
late.join();
closer.join();
poller.join();
if (at_close < 1)
return "the close did not begin its wait with the poller counted inside, so nothing held it "
"open for a call to race";
if (late_poll.load() != -1)
return "cultmesh_quic_next_event returned " + std::to_string(late_poll.load()) +
" to a call made during the close, not the -1 that refuses it";
if (late_error.load() != -1)
return "cultmesh_quic_last_error returned " + std::to_string(late_error.load()) +
" to a call made during the close, not the -1 that refuses it";
return {};
}
// How long `holdtimeout`'s two polls ask for, and how long the hold stays armed
// under them. The gap between each poll and the hold is the whole measurement:
// a poll the hold parks leaves when the hold is released, so its elapsed time
// lands at the last number instead of its own.
//
// Two polls, a short one and a long one, because a guard that tests the timeout
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
// instead of the wake — park it unless it was long — is identity on any single
// value on one side of its threshold. The long one sits above 1500, the stated
// ceiling on how long the hold guard may treat a long wait as surely worth
// parking; the hold stays armed well past it so a guard that parks anyway is
// held for the rest of the arming instead of leaving on its own timeout, which
// is the overshoot this scenario's late tolerance is wide enough to see.
constexpr int32_t kHeldTimeoutPollMs = 150;
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
constexpr int32_t kHeldLongTimeoutPollMs = 1600;
constexpr int kHoldArmedMs = 3000;
// The seam's own rule, and the only scenario that can see it: the hold parks a
// call the wait woke, and not one whose own timeout expired.
//
// Everything else that arms the hold parks a woken call, so the guard governs
// nothing either of them observes — the timeout scenario never arms the hold,
// and the hold scenarios never let a timeout expire, so nothing was ever in
// both states at once and the guard could be deleted with every scenario still
// green. This puts calls in both states: the hold is armed, and the runtime is
// idle, so each wait ends on its timeout and the call is on its way out.
//
// It matters because a scenario that parked such a call would then report the
// bridge keeping a host call inside the library when what kept it was the
// fixture — the quiesce numbers `holdclose` asserts would be the seam's, not
// the bridge's.
std::string HoldTimeoutOnce(std::array<Overshoot, 2>& overshoot) {
cultmesh_quic_debug_hold_calls(1);
void* runtime = nullptr;
const int32_t opened = cultmesh_quic_runtime_open("cultmesh-quic-native-tests", &runtime);
if (opened != 0 || runtime == nullptr) {
cultmesh_quic_debug_hold_calls(0);
return "cultmesh_quic_runtime_open returned " + std::to_string(opened);
}
// Each on a thread of its own, because a parked call does not come back until
// the release below and this thread is what releases it.
constexpr std::array<int32_t, 2> timeouts{kHeldTimeoutPollMs, kHeldLongTimeoutPollMs};
std::array<TimedPoll, 2> polls{};
std::array<std::thread, 2> pollers;
for (size_t index = 0; index < timeouts.size(); ++index)
pollers[index] = std::thread([runtime, &polls, &timeouts, index] {
polls[index] = PollFor(runtime, timeouts[index]);
});
std::this_thread::sleep_for(std::chrono::milliseconds(kHoldArmedMs));
cultmesh_quic_debug_hold_calls(0);
for (auto& poller : pollers) poller.join();
cultmesh_quic_runtime_close(runtime);
for (size_t index = 0; index < timeouts.size(); ++index) {
const TimedPoll& poll = polls[index];
const int32_t timeout_ms = timeouts[index];
overshoot[index].Record(poll, timeout_ms);
if (poll.result != 0)
return "a poll ended by its own timeout returned " + std::to_string(poll.result) +
", not the 0 an idle runtime owes it";
if (poll.elapsed_ms < timeout_ms - kEarlyToleranceMs)
return "a poll asking for " + std::to_string(timeout_ms) + " ms returned after " +
std::to_string(poll.elapsed_ms) + " ms, so the fixture never let its timeout run out";
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
if (poll.elapsed_ms > timeout_ms + kGenerousLateToleranceMs)
return "a poll asking for " + std::to_string(timeout_ms) + " ms returned after " +
std::to_string(poll.elapsed_ms) + " ms: the hold parked a call its own timeout had "
"already ended, and it left when the hold did";
}
return {};
}
int HoldTimeout(int iterations) {
std::array<Overshoot, 2> overshoot{};
for (int iteration = 0; iteration < iterations; ++iteration) {
const std::string failure = HoldTimeoutOnce(overshoot);
if (!failure.empty())
return Fail("holdtimeout iteration " + std::to_string(iteration) + ": " + failure);
}
std::printf("holdtimeout %dx: ok (worst overshoot %lld ms at %d, %lld ms at %d)\n", iterations,
overshoot[0].worst_ms, kHeldTimeoutPollMs, overshoot[1].worst_ms, kHeldLongTimeoutPollMs);
return 0;
}
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
int LateCall(int iterations) {
for (int iteration = 0; iteration < iterations; ++iteration) {
const std::string failure = LateCallOnce();
if (!failure.empty())
return Fail("latecall iteration " + std::to_string(iteration) + ": " + failure);
}
std::printf("latecall %dx: ok\n", iterations);
return 0;
}
int HoldClose(int iterations, int pollers) {
for (int iteration = 0; iteration < iterations; ++iteration) {
const std::string failure = HoldCloseOnce(pollers);
if (!failure.empty())
return Fail("holdclose iteration " + std::to_string(iteration) + ": " + failure);
}
std::printf("holdclose %dx%d: ok\n", iterations, pollers);
return 0;
}
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
// The values `waitseam` asks for: the smallest, the seam's own sentinel-versus-
// zero boundary, one on either side of a 16 ms quantum and of a 40 ms one, the
// ordinary `pollbusy` timeout and its neighbours, `polltimeout`'s long probe,
// and INT32_MAX. Nothing in section 6 of the header states a maximum on
// `timeout_ms` narrower than what `int32_t` itself holds, so INT32_MAX is the
// ceiling this checks and nothing wider is claimed.
constexpr std::array<int32_t, 11> kWaitSeamProbesMs{
0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300, INT32_MAX};
// How long a probe's own poller thread is given to reach the wait before this
// gives up on it and fails with the probe named, rather than hanging.
constexpr int kWaitSeamDeadlineMs = 2000;
// One probe: ask a fresh, otherwise-idle runtime for `timeout_ms` and read
// `cultmesh_quic_debug_last_wait_ms` — the bridge's own account of what it
// handed its condition wait — instead of measuring how long the call took.
// Every clamp, floor, round, scale, offset and later-poll mapping Soul's seven
// passes found is the identity at some wall-clock probe; none of them is the
// identity here unless it is also the identity at every value below, because
// this reads the argument itself rather than inferring it from elapsed time.
//
// `timeout_ms <= 0` never reaches the wait at all — `cultmesh_quic_next_event`
// returns at once when the queue is empty — so there is nothing for the seam to
// record, and the honest check is the converse: it must still say nothing,
// which is exactly what a seam that recorded the raw argument on every call,
// instead of only the value it actually handed its wait, would get wrong.
std::string WaitSeamProbeOnce(int32_t timeout_ms) {
void* runtime = nullptr;
const int32_t opened = cultmesh_quic_runtime_open("cultmesh-quic-native-tests", &runtime);
if (opened != 0 || runtime == nullptr)
return "cultmesh_quic_runtime_open returned " + std::to_string(opened);
cultmesh_quic_debug_reset_last_wait_ms();
std::string failure;
if (timeout_ms <= 0) {
std::vector<uint8_t> payload(static_cast<size_t>(kPayloadCapacity));
cultmesh_quic_event event{};
int32_t required = 0;
const int32_t result = cultmesh_quic_next_event(
runtime, timeout_ms, &event, payload.data(), kPayloadCapacity, &required);
const int32_t recorded = cultmesh_quic_debug_last_wait_ms();
if (result != 0)
failure = "a poll asking for " + std::to_string(timeout_ms) + " ms returned " +
std::to_string(result) + ", not the 0 an idle runtime owes it";
else if (recorded != -1)
failure = "a poll asking for " + std::to_string(timeout_ms) + " ms recorded " +
std::to_string(recorded) + " ms on the wait seam, though the bridge's own guard "
"never enters a wait for a timeout that is not greater than zero";
cultmesh_quic_runtime_close(runtime);
return failure;
}
// A real wait, on a thread of its own: a probe at INT32_MAX has to be
// released by the close, not waited out.
std::thread poller([runtime, timeout_ms] {
std::vector<uint8_t> payload(static_cast<size_t>(kPayloadCapacity));
cultmesh_quic_event event{};
int32_t required = 0;
cultmesh_quic_next_event(runtime, timeout_ms, &event, payload.data(), kPayloadCapacity, &required);
});
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(kWaitSeamDeadlineMs);
int32_t recorded = cultmesh_quic_debug_last_wait_ms();
while (recorded == -1 && std::chrono::steady_clock::now() < deadline) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
recorded = cultmesh_quic_debug_last_wait_ms();
}
cultmesh_quic_runtime_close(runtime);
poller.join();
if (recorded != timeout_ms)
failure = "a poll asking for " + std::to_string(timeout_ms) + " ms recorded " +
std::to_string(recorded) + " ms on the wait seam: the bridge handed its condition "
"wait something other than the host's own argument";
return failure;
}
// Past the twelfth poll on one runtime: no scenario before this one ever asked
// a runtime for a thirteenth, so a wait that only changes shape after twelve
// had nowhere to be seen. Forty short waits on a runtime that stays open for
// all of them, each checked by equality.
constexpr int32_t kWaitSeamRepeatedMs = 5;
constexpr int kWaitSeamRepeatedPolls = 40;
std::string WaitSeamRepeatedOnce() {
void* runtime = nullptr;
const int32_t opened = cultmesh_quic_runtime_open("cultmesh-quic-native-tests", &runtime);
if (opened != 0 || runtime == nullptr)
return "cultmesh_quic_runtime_open returned " + std::to_string(opened);
std::string failure;
for (int poll = 0; poll < kWaitSeamRepeatedPolls && failure.empty(); ++poll) {
cultmesh_quic_debug_reset_last_wait_ms();
std::vector<uint8_t> payload(static_cast<size_t>(kPayloadCapacity));
cultmesh_quic_event event{};
int32_t required = 0;
cultmesh_quic_next_event(
runtime, kWaitSeamRepeatedMs, &event, payload.data(), kPayloadCapacity, &required);
const int32_t recorded = cultmesh_quic_debug_last_wait_ms();
if (recorded != kWaitSeamRepeatedMs)
failure = "poll " + std::to_string(poll + 1) + " of " +
std::to_string(kWaitSeamRepeatedPolls) + " on one runtime recorded " +
std::to_string(recorded) + " ms, not " + std::to_string(kWaitSeamRepeatedMs);
}
cultmesh_quic_runtime_close(runtime);
return failure;
}
int WaitSeam(int iterations) {
for (int iteration = 0; iteration < iterations; ++iteration) {
for (int32_t probe : kWaitSeamProbesMs) {
const std::string failure = WaitSeamProbeOnce(probe);
if (!failure.empty())
return Fail("waitseam iteration " + std::to_string(iteration) + " probe " +
std::to_string(probe) + ": " + failure);
}
const std::string repeated = WaitSeamRepeatedOnce();
if (!repeated.empty())
return Fail("waitseam iteration " + std::to_string(iteration) + ": " + repeated);
}
std::printf("waitseam %dx: ok (%zu probes, %d repeated polls on one runtime)\n",
iterations, kWaitSeamProbesMs.size(), kWaitSeamRepeatedPolls);
return 0;
}
#endif // CULTMESH_QUIC_DEBUG_ASSERTS
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
// How long `pollhammer`'s idle poll asks for. One host thread hammers every
// gate-touching entry point that cannot itself queue an event, in a yield
// loop, while another holds this one poll idle; each hammered call's
// `CallScope` destructor notifies every waiter on the runtime's condition
// variable (cultmesh_quic_native.cpp:286), so the idle poll is woken many times
// over for every timeout it is given here. That is exactly the shape a wake
// count or an error-state predicate exploits, and exactly what a revert that
// returns on any wake at all fails immediately.
//
// `cultmesh_quic_connection_open`, `cultmesh_quic_listener_open` and
// `cultmesh_quic_stream_open` touch the same gate but can each queue a real
// event on this runtime, and a genuine event is allowed to end the idle poll
// early — hammering with those would make an early return ambiguous between
// the fault this scenario hunts and a real delivery, so they are left out. The
// refused `listener_open` below is a setup step run once, not part of the
// loop, and it leaves no listener behind.
constexpr int32_t kHammerPollMs = 1000;
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
// A self-signed ECDSA P-256 PKCS12, generated once with OpenSSL and embedded so
// this scenario needs no crypto library of its own and no network fetch:
// openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
// -keyout key.pem -out cert.pem -days 3650 -nodes -subj "/CN=cultmesh-quic-native-tests"
// openssl pkcs12 -export -out test.p12 -inkey key.pem -in cert.pem -passout pass:
// Used only to give pollhammer's live-objects run a listener that can actually
// load a credential; nothing ever connects to it, so it never accepts and never
// queues an event this scenario would have to tell apart from a bug.
constexpr std::array<uint8_t, 1088> kSelfSignedPkcs12{{
0x30, 0x82, 0x04, 0x3c, 0x02, 0x01, 0x03, 0x30, 0x82, 0x03, 0xf2, 0x06,
0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x82,
0x03, 0xe3, 0x04, 0x82, 0x03, 0xdf, 0x30, 0x82, 0x03, 0xdb, 0x30, 0x82,
0x02, 0x8a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07,
0x06, 0xa0, 0x82, 0x02, 0x7b, 0x30, 0x82, 0x02, 0x77, 0x02, 0x01, 0x00,
0x30, 0x82, 0x02, 0x70, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
0x01, 0x07, 0x01, 0x30, 0x5f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
0x0d, 0x01, 0x05, 0x0d, 0x30, 0x52, 0x30, 0x31, 0x06, 0x09, 0x2a, 0x86,
0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c, 0x30, 0x24, 0x04, 0x10, 0x48,
0xdf, 0x04, 0x5a, 0x31, 0x34, 0x6d, 0xe0, 0x7c, 0xde, 0x3c, 0xa2, 0xde,
0xf2, 0x8d, 0x20, 0x02, 0x02, 0x08, 0x00, 0x30, 0x0c, 0x06, 0x08, 0x2a,
0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09, 0x05, 0x00, 0x30, 0x1d, 0x06,
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2a, 0x04, 0x10,
0x44, 0x04, 0xd9, 0x9b, 0x99, 0x3a, 0xcb, 0x89, 0xc0, 0xcb, 0x53, 0xe3,
0xe3, 0x5b, 0xb4, 0x48, 0x80, 0x82, 0x02, 0x00, 0x92, 0xcc, 0x04, 0xcf,
0x7d, 0x58, 0xec, 0x68, 0x1a, 0x1b, 0xd5, 0x3b, 0x17, 0x57, 0x22, 0xd5,
0x45, 0x00, 0xff, 0x05, 0x0f, 0x0d, 0x18, 0x8b, 0x72, 0x5b, 0xcc, 0xd9,
0x87, 0xf4, 0x99, 0x44, 0x7c, 0xd6, 0xc3, 0x02, 0x2a, 0xaf, 0xd9, 0x10,
0x14, 0xd3, 0x9f, 0x1a, 0x2c, 0x7b, 0x4f, 0xe6, 0x0b, 0x1d, 0x7f, 0xb9,
0xf4, 0x01, 0x5c, 0x4b, 0xd5, 0xa1, 0x57, 0xb0, 0x3d, 0xe0, 0xbc, 0x70,
0x72, 0xe0, 0xe9, 0xb8, 0x9a, 0xde, 0x68, 0xfe, 0x7c, 0xc7, 0xca, 0xcc,
0x61, 0x32, 0x88, 0x72, 0xdd, 0x97, 0x28, 0x7b, 0x5b, 0x0d, 0x30, 0xf5,
0xa2, 0xd1, 0xe8, 0x89, 0xd4, 0x1d, 0x88, 0x28, 0xd2, 0xc3, 0x02, 0x93,
0xa1, 0x27, 0xfb, 0x4a, 0xe7, 0xdc, 0xac, 0xf3, 0x0d, 0xa9, 0xd3, 0x14,
0x37, 0x71, 0xb7, 0xe5, 0xaf, 0xdb, 0xb6, 0xda, 0x42, 0xb7, 0x24, 0x7a,
0x94, 0xad, 0x73, 0x79, 0xde, 0x21, 0xb9, 0x13, 0x63, 0x98, 0x33, 0x62,
0x4a, 0x36, 0xb8, 0x6d, 0x14, 0x8b, 0x84, 0xf0, 0x8e, 0x55, 0xcc, 0x95,
0x14, 0x54, 0xcc, 0x68, 0x4b, 0x09, 0x24, 0xb4, 0x79, 0x49, 0x05, 0xc7,
0x0e, 0xfe, 0xed, 0x23, 0x8c, 0x87, 0xc6, 0xc3, 0x76, 0x25, 0xd9, 0xd6,
0xae, 0x5a, 0xf4, 0x86, 0x74, 0xb8, 0x44, 0x22, 0xe8, 0xfc, 0xa5, 0xc2,
0x25, 0xbd, 0x84, 0xfd, 0x92, 0x96, 0xe2, 0x66, 0xa9, 0xb0, 0x1f, 0xd3,
0x8d, 0xf7, 0x1c, 0xce, 0xed, 0x09, 0x11, 0x61, 0xec, 0x2d, 0xa4, 0x9d,
0xb9, 0xdb, 0xd4, 0x2e, 0xa4, 0xff, 0xe6, 0x09, 0x8e, 0x9a, 0x0b, 0xdf,
0x30, 0xf4, 0xea, 0xe6, 0x09, 0x2d, 0x16, 0xec, 0x5d, 0xb6, 0x39, 0xda,
0xd4, 0x43, 0x41, 0x95, 0xc8, 0x32, 0xd2, 0xac, 0x17, 0x58, 0x05, 0x7e,
0x10, 0x24, 0xae, 0xd6, 0x58, 0x80, 0x0a, 0x29, 0xcb, 0x4b, 0x78, 0x62,
0xbe, 0xf6, 0x6c, 0x7d, 0x44, 0x07, 0x7b, 0xfb, 0xd9, 0xb6, 0x74, 0x75,
0x9f, 0xe6, 0xb4, 0xc3, 0x54, 0x53, 0x46, 0xdb, 0xf2, 0xa1, 0x47, 0xb8,
0xf1, 0xff, 0x01, 0xba, 0xde, 0xf9, 0xca, 0xd6, 0xc0, 0x38, 0x40, 0xd0,
0x86, 0x72, 0xc8, 0xcf, 0xdd, 0xd6, 0xf6, 0x36, 0xe0, 0xf3, 0x2e, 0x95,
0x23, 0xdb, 0x99, 0x72, 0x1c, 0xa6, 0xa0, 0xf7, 0x17, 0x93, 0x1e, 0x20,
0x6c, 0x75, 0x25, 0x0e, 0x1c, 0xab, 0xa4, 0xce, 0x35, 0xd4, 0xf4, 0xc6,
0x63, 0x89, 0x0d, 0xa9, 0x91, 0x0b, 0x7e, 0xce, 0x7d, 0x4d, 0x5e, 0xbf,
0xfd, 0xa9, 0xbd, 0xb6, 0x1e, 0x2b, 0x08, 0x5d, 0x7a, 0xdd, 0x61, 0xdf,
0x24, 0xe0, 0xff, 0x4b, 0xeb, 0xa2, 0xeb, 0xb6, 0x2f, 0x2c, 0xbf, 0x9b,
0x5f, 0x6a, 0x1a, 0x33, 0xae, 0x02, 0x6d, 0xbf, 0x8e, 0x6f, 0xeb, 0xda,
0x17, 0x88, 0x79, 0xf8, 0xe0, 0x1c, 0x1d, 0x64, 0x7c, 0xc3, 0xc2, 0xc1,
0xce, 0x12, 0x67, 0x74, 0xbc, 0xef, 0x9f, 0x2f, 0x35, 0xef, 0x40, 0x24,
0xc1, 0x5f, 0x82, 0xc9, 0x00, 0x2c, 0xbf, 0x2b, 0xbf, 0x64, 0x39, 0x63,
0x0c, 0xcd, 0x6f, 0xe5, 0x18, 0xfa, 0xeb, 0x02, 0x61, 0xc3, 0x22, 0xb0,
0xfd, 0xfb, 0x9d, 0x32, 0xbb, 0x2f, 0xd8, 0x9d, 0x63, 0x8d, 0x9e, 0xb1,
0x5a, 0x7d, 0xc3, 0xe5, 0xcc, 0xae, 0xe4, 0x71, 0x0d, 0x7b, 0xa9, 0x80,
0xbd, 0xef, 0x86, 0x12, 0x13, 0x42, 0xf8, 0x14, 0xcb, 0xa9, 0xfd, 0x86,
0xea, 0xd5, 0x81, 0x54, 0x87, 0xbc, 0x66, 0x24, 0xfd, 0x12, 0xf5, 0x6b,
0xf4, 0x45, 0x7e, 0x41, 0x8e, 0x00, 0xf3, 0x63, 0xc7, 0x24, 0xc5, 0xa1,
0xfd, 0x70, 0xbc, 0xfa, 0x26, 0xa0, 0x07, 0xc2, 0x97, 0xb1, 0xe0, 0x42,
0x5a, 0x2b, 0x65, 0xec, 0xda, 0x45, 0x79, 0xe5, 0x18, 0xf2, 0xce, 0x8e,
0xc3, 0xe3, 0x74, 0x6d, 0x30, 0x82, 0x01, 0x49, 0x06, 0x09, 0x2a, 0x86,
0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x82, 0x01, 0x3a, 0x04,
0x82, 0x01, 0x36, 0x30, 0x82, 0x01, 0x32, 0x30, 0x82, 0x01, 0x2e, 0x06,
0x0b, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x02,
0xa0, 0x81, 0xf7, 0x30, 0x81, 0xf4, 0x30, 0x5f, 0x06, 0x09, 0x2a, 0x86,
0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0d, 0x30, 0x52, 0x30, 0x31, 0x06,
0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c, 0x30, 0x24,
0x04, 0x10, 0xd8, 0x91, 0xb9, 0x92, 0x3a, 0x1e, 0x6b, 0xa2, 0x57, 0x44,
0x16, 0x3a, 0xea, 0x36, 0x8d, 0x60, 0x02, 0x02, 0x08, 0x00, 0x30, 0x0c,
0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09, 0x05, 0x00,
0x30, 0x1d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01,
0x2a, 0x04, 0x10, 0x35, 0x48, 0x03, 0x23, 0x6a, 0x3d, 0x5f, 0x96, 0xb7,
0xfb, 0xb1, 0xcb, 0xdc, 0x0c, 0x1f, 0x79, 0x04, 0x81, 0x90, 0x4d, 0x36,
0x5b, 0x1e, 0xc1, 0xc1, 0xa3, 0xfa, 0x85, 0xe6, 0x58, 0x89, 0x5d, 0xbe,
0x17, 0xb4, 0x90, 0x96, 0x2d, 0x3d, 0x32, 0xe2, 0x73, 0x6c, 0x2c, 0xfd,
0x7b, 0xe4, 0x9c, 0x82, 0x3c, 0xe4, 0x7a, 0x58, 0x45, 0xdd, 0xcd, 0x00,
0xa2, 0x52, 0xf9, 0x98, 0xd4, 0x0f, 0x64, 0x0b, 0x58, 0x27, 0xfe, 0xfc,
0xfd, 0x60, 0x3f, 0x7e, 0x6f, 0x73, 0x73, 0xda, 0x16, 0x34, 0x83, 0x38,
0x89, 0x3d, 0xbe, 0xde, 0x0a, 0x47, 0x7b, 0xcd, 0xc1, 0x88, 0x72, 0x38,
0x95, 0xb5, 0x54, 0xfa, 0x2a, 0x84, 0x2b, 0xcd, 0x98, 0xe3, 0x20, 0xd2,
0xac, 0xd8, 0xf5, 0xa8, 0xd2, 0xef, 0x1f, 0x99, 0x88, 0x16, 0x6f, 0xf2,
0x08, 0xe9, 0x28, 0xc8, 0x7a, 0x02, 0x57, 0x5c, 0x1c, 0x94, 0x0f, 0x57,
0xfc, 0x0e, 0x85, 0x63, 0xc5, 0x44, 0x45, 0x4f, 0xc6, 0x35, 0x52, 0x3d,
0xd4, 0xe2, 0x22, 0x6f, 0x72, 0xde, 0x55, 0xad, 0x20, 0x84, 0x91, 0x14,
0x7e, 0x5f, 0x98, 0x2a, 0x5e, 0x2b, 0xf4, 0xc5, 0x10, 0x1b, 0x31, 0x25,
0x30, 0x23, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09,
0x15, 0x31, 0x16, 0x04, 0x14, 0xbd, 0x8a, 0x08, 0x3c, 0x66, 0xf7, 0x9d,
0xa0, 0x25, 0x8b, 0x33, 0xac, 0xc2, 0x5e, 0xdc, 0x76, 0xd4, 0x33, 0x1a,
0xd6, 0x30, 0x41, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20, 0x4e, 0x6f,
0x75, 0x90, 0x57, 0xb4, 0x39, 0x1e, 0xc3, 0x97, 0x50, 0x5e, 0x4d, 0xf2,
0xa8, 0xf9, 0x0e, 0xcd, 0x7f, 0x57, 0x31, 0xae, 0xf2, 0x87, 0x0a, 0x55,
0xb0, 0xbc, 0xd6, 0x11, 0x83, 0xc3, 0x04, 0x08, 0x52, 0x79, 0xb1, 0x83,
0xe6, 0xe4, 0x6e, 0x4d, 0x02, 0x02, 0x08, 0x00,
}};
std::string PollHammerOnce(bool with_recorded_error, bool with_live_objects) {
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
void* runtime = nullptr;
const int32_t opened = cultmesh_quic_runtime_open("cultmesh-quic-native-tests", &runtime);
if (opened != 0 || runtime == nullptr)
return "cultmesh_quic_runtime_open returned " + std::to_string(opened);
if (with_recorded_error) {
// Not a certificate at all: PKCS12 parsing fails before any network
// I/O, so this is refused synchronously and leaves no listener behind,
// and `runtime->error` is non-empty for the rest of this runtime's life.
const uint8_t junk[] = {0x00, 0x01, 0x02, 0x03};
uint64_t listener_id = 0;
uint16_t bound_port = 0;
const int32_t result = cultmesh_quic_listener_open(
runtime, nullptr, 0, junk, static_cast<int32_t>(sizeof(junk)), "", &listener_id, &bound_port);
if (result >= 0) {
cultmesh_quic_runtime_close(runtime);
return "cultmesh_quic_listener_open accepted a junk PKCS12 credential";
}
}
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
// Self's ruling of 2026-09-22, F3: the predicate covered by the hammer set
// above is only ever exercised on a runtime with nothing live in it. An
// unroutable address (RFC 5737 TEST-NET-1) gets a connection into
// `runtime->connections` at once — `cultmesh_quic_connection_open` inserts
// it before the handshake starts — and that handshake then never completes
// inside this scenario's poll window, so it queues no event of its own. A
// best-effort stream rides the same connection. The listener is real (a
// credential MsQuic actually loads) but nothing ever connects to it, so it
// never accepts and never queues an event either: every object here is
// live in the runtime's maps without ever waking the idle poll on its own.
uint64_t live_connection_id = 0;
uint64_t live_stream_id = 0;
uint64_t live_listener_id = 0;
if (with_live_objects) {
const int32_t connecting =
cultmesh_quic_connection_open(runtime, "192.0.2.1", 9, &live_connection_id);
if (connecting != 0) {
cultmesh_quic_runtime_close(runtime);
return "cultmesh_quic_connection_open (live-objects setup) returned " +
std::to_string(connecting);
}
// Best-effort: whether this succeeds is not the point of the
// scenario, and P1's predicate names `connections`, not `streams`.
cultmesh_quic_stream_open(
runtime, live_connection_id, CULTMESH_QUIC_STREAM_RELIABLE, &live_stream_id);
uint16_t bound_port = 0;
cultmesh_quic_listener_open(runtime, "127.0.0.1", 0, kSelfSignedPkcs12.data(),
static_cast<int32_t>(kSelfSignedPkcs12.size()), "", &live_listener_id, &bound_port);
}
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
std::atomic<bool> hammering{true};
std::thread hammer([runtime, &hammering] {
constexpr uint64_t kBogusId = 0xffffffffffffffffull;
const uint8_t frame[] = {0x00};
std::array<char, 256> message{};
while (hammering.load(std::memory_order_relaxed)) {
cultmesh_quic_last_error(runtime, message.data(), static_cast<int32_t>(message.size()));
cultmesh_quic_last_status(runtime);
cultmesh_quic_connection_shutdown(runtime, kBogusId, 0);
cultmesh_quic_stream_shutdown(runtime, kBogusId, 0);
cultmesh_quic_stream_send_frame(runtime, kBogusId, frame, 1, 0);
cultmesh_quic_connection_certificate_complete(runtime, kBogusId, 0);
cultmesh_quic_listener_close(runtime, kBogusId);
std::this_thread::yield();
}
});
const TimedPoll poll = PollFor(runtime, kHammerPollMs);
hammering.store(false, std::memory_order_relaxed);
hammer.join();
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
if (live_listener_id != 0) cultmesh_quic_listener_close(runtime, live_listener_id);
if (live_connection_id != 0) cultmesh_quic_connection_shutdown(runtime, live_connection_id, 0);
(void)live_stream_id;
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
cultmesh_quic_runtime_close(runtime);
if (poll.result != 0)
return "the idle poll returned " + std::to_string(poll.result) +
", not the 0 an idle runtime owes it";
if (poll.elapsed_ms < kHammerPollMs - kEarlyToleranceMs)
return "the idle poll returned after " + std::to_string(poll.elapsed_ms) +
" ms while another thread hammered the gate, ending its wait early";
if (poll.elapsed_ms > static_cast<long long>(kHammerPollMs) + kGenerousLateToleranceMs)
return "the idle poll returned after " + std::to_string(poll.elapsed_ms) +
" ms: well past its own timeout";
return {};
}
int PollHammer(int iterations) {
for (int iteration = 0; iteration < iterations; ++iteration) {
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
std::string failure = PollHammerOnce(false, false);
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
if (!failure.empty())
return Fail("pollhammer iteration " + std::to_string(iteration) + " (clean runtime): " + failure);
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
failure = PollHammerOnce(true, false);
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
if (!failure.empty())
return Fail("pollhammer iteration " + std::to_string(iteration) +
" (after a refused call recorded an error): " + failure);
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
failure = PollHammerOnce(false, true);
if (!failure.empty())
return Fail("pollhammer iteration " + std::to_string(iteration) +
" (with a live connection, stream and listener): " + failure);
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
}
std::printf("pollhammer %dx: ok\n", iterations);
return 0;
}
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
// Self's ruling of 2026-09-22, F4: any `timeout_ms <= 0` has to return within a
// small, generously-margined bound, and a negative timeout has to be probed
// too — the bridge's own guard (`timeout_ms > 0`) already treats zero and
// negative alike, so 0, -1 and INT32_MIN all take the same fast path and none
// of them may collide with the seam's own "nothing recorded" sentinel.
constexpr std::array<int32_t, 3> kZeroTimeoutProbesMs{0, -1, INT32_MIN};
// Generous next to `kEarlyToleranceMs`/`kGenerousLateToleranceMs` because a
// non-positive timeout never enters the condition wait at all, so this is
// bounding a runtime lock acquisition and an empty-queue check, not a
// scheduler wakeup — the margin only has to be well clear of a mistake that
// makes this path sleep or spin, not of ordinary jitter.
constexpr int kZeroTimeoutBoundMs = 50;
std::string ZeroTimeoutOnce(int32_t timeout_ms) {
void* runtime = nullptr;
const int32_t opened = cultmesh_quic_runtime_open("cultmesh-quic-native-tests", &runtime);
if (opened != 0 || runtime == nullptr)
return "cultmesh_quic_runtime_open returned " + std::to_string(opened);
const TimedPoll poll = PollFor(runtime, timeout_ms);
cultmesh_quic_runtime_close(runtime);
if (poll.result != 0)
return "a poll asking for " + std::to_string(timeout_ms) + " ms returned " +
std::to_string(poll.result) + ", not the 0 an idle runtime owes it";
if (poll.elapsed_ms > kZeroTimeoutBoundMs)
return "a poll asking for " + std::to_string(timeout_ms) + " ms returned after " +
std::to_string(poll.elapsed_ms) + " ms: a non-positive timeout must return within " +
std::to_string(kZeroTimeoutBoundMs) + " ms, not stall or spin";
return {};
}
int ZeroTimeout(int iterations) {
for (int iteration = 0; iteration < iterations; ++iteration) {
for (int32_t probe : kZeroTimeoutProbesMs) {
const std::string failure = ZeroTimeoutOnce(probe);
if (!failure.empty())
return Fail("zerotimeout iteration " + std::to_string(iteration) + " probe " +
std::to_string(probe) + ": " + failure);
}
}
std::printf("zerotimeout %dx: ok (%zu probes)\n", iterations, kZeroTimeoutProbesMs.size());
return 0;
}
int CloseRace(int iterations, int pollers) {
for (int iteration = 0; iteration < iterations; ++iteration) {
const std::string failure = CloseRaceOnce(pollers);
if (!failure.empty())
return Fail("closerace iteration " + std::to_string(iteration) + ": " + failure);
}
std::printf("closerace %dx%d: ok\n", iterations, pollers);
return 0;
}
} // namespace
int main(int argc, char** argv) {
if (argc < 2) {
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
std::fprintf(stderr,
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
"usage: %s closerace|holdclose|holdtimeout|polltimeout|pollbusy|pollhammer|zerotimeout|"
"payloadfit|latecall|waitseam [iterations] [pollers]\n",
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
argv[0]);
return 2;
}
const std::string scenario = argv[1];
const int iterations = argc > 2 ? std::atoi(argv[2]) : 20;
const int pollers = argc > 3 ? std::atoi(argv[3]) : 256;
if (iterations <= 0 || pollers <= 0) {
std::fprintf(stderr, "%s needs a positive iteration and poller count\n", scenario.c_str());
return 2;
}
if (scenario == "closerace") return CloseRace(iterations, pollers);
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
if (scenario == "polltimeout") return PollTimeout(iterations);
if (scenario == "pollbusy") return PollBusy(iterations);
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
if (scenario == "pollhammer") return PollHammer(iterations);
Land the QUIC Cut 3 eighth fix batch: release config, whole-duration seam, pollhammer live objects, zero-timeout bound, closerace starvation fix Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:27:56 +02:00
if (scenario == "zerotimeout") return ZeroTimeout(iterations);
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
if (scenario == "payloadfit") return PayloadFit(iterations);
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
if (scenario == "holdclose" || scenario == "latecall" || scenario == "holdtimeout" ||
scenario == "waitseam") {
#if defined(CULTMESH_QUIC_DEBUG_ASSERTS)
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
if (scenario == "latecall") return LateCall(iterations);
if (scenario == "holdtimeout") return HoldTimeout(iterations);
Record the wait, not its shadow: a dev-only seam for cultmesh_quic_next_event Self's ruling of 2026-09-22 called it after seven Soul passes: a wall-clock probe against cultmesh_quic_next_event's timeout can never tell the host's argument from a clamp, floor, round, scale or later-poll fault that happens to be the identity at the probe's own value, because there is always another function that matches. Move the observation to where the rule is decided. Add cultmesh_quic_debug_last_wait_ms / _reset_last_wait_ms, built exactly like the existing hold seam: gated behind CULTMESH_QUIC_DEBUG_ASSERTS, absent from a shipped build. CULTMESH_QUIC_DEBUG_RECORD_WAIT wraps the literal timeout_ms handed to the condition wait as a pass-through, so what is recorded and what is actually waited on are the same evaluation and cannot diverge except by changing what the macro wraps. Two scenarios: - waitseam asserts the recorded wait equals the host's own argument, exactly, over 0, 1, 15, 16, 39, 40, 999, 1000, 1001, 7300 and INT32_MAX (the header states no narrower maximum), and over 40 polls on one runtime past the point every prior scenario stopped asking. timeout_ms <= 0 never reaches the wait at all, so its probe checks the honest converse — the seam stays untouched — which is what catches a seam that records the raw argument on every call instead of only the value it actually hands its wait. - pollhammer runs one thread hammering every gate-touching entry point that cannot itself queue an event, in a yield loop, against another thread's idle poll, once on a clean runtime and once after a refused listener_open has left runtime->error non-empty. The idle poll must still stay for its timeout. polltimeout, pollbusy and holdtimeout stay as generous-margin proof that the recorded wait is really waited, not as the precision instrument they used to have to be: the fastest-of-ten mechanism, the 15 ms probe's tight tolerance, and the stated-limits table they rested on are gone, because waitseam now kills every clamp, floor, round, scale, offset and later-poll mutant by equality regardless of timing. The Windows priority raise these three scenarios carried is gone with them: a flat 800 ms late tolerance already covers the worst overshoot the old table recorded (127 ms at normal priority under load), so raising the process above the load bought nothing the new margin does not already have. holdtimeout's long probe moves to 1600 ms, above the stated 1500 ms ceiling on the hold guard, with the hold kept armed to 3000 ms so a guard that parks a naturally-timed-out call anyway overshoots by well more than the margin. Verified locally on win32-x64: waitseam and pollhammer pass against the unmutated bridge; a seam-loosening mutant (recording the raw argument outside the wait's own branch) fails waitseam's 0 ms probe; an inline +25 ms offset on the wait's argument fails waitseam by exact mismatch; the S5 hold-guard mutant (timeout_ms > 1500) fails holdtimeout by 1.4 s overshoot; K1 (++wakes > 64), K2 (!error.empty()) and a no-predicate revert all fail pollhammer's idle poll within tens of milliseconds. All were hand-reverted and diffed clean against the working tree before this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 05:33:19 +02:00
if (scenario == "waitseam") return WaitSeam(iterations);
return HoldClose(iterations, pollers);
#else
std::fprintf(stderr,
Let the bridge's own timeout, refusal and two-phase poll be seen Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:09:50 +02:00
"%s needs the development seam: configure with "
"-DCULTMESH_QUIC_DEBUG_ASSERTS=ON\n", scenario.c_str());
return 2;
#endif
}
std::fprintf(stderr, "unknown scenario '%s'\n", scenario.c_str());
return 2;
}