CultLib/native/GameCult.Mesh.Quic.Native/tests/cultmesh_quic_native_tests.cpp
Meta Vens 44ab3767ce 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

1305 lines
68 KiB
C++

// 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]
// cultmesh_quic_native_tests polltimeout [iterations]
// cultmesh_quic_native_tests pollbusy [iterations]
// cultmesh_quic_native_tests latecall [iterations]
// cultmesh_quic_native_tests holdtimeout [iterations]
// cultmesh_quic_native_tests waitseam [iterations]
// cultmesh_quic_native_tests pollhammer [iterations]
// 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.
//
// 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.
//
// 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>
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <thread>
#include <vector>
namespace {
// `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};
#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();
#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.
const auto counted_deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(kCloseRaceCountedDeadlineMs);
while (cultmesh_quic_debug_peak_calls() < pollers &&
std::chrono::steady_clock::now() < counted_deadline)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// 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";
}
// `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.
std::this_thread::sleep_for(std::chrono::milliseconds(kCloseRaceAssertsSettleMs));
#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.
std::this_thread::sleep_for(std::chrono::milliseconds(kCloseRaceNoSeamSettleMs));
#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 {};
}
// 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.
constexpr int32_t kShortestPollMs = 15;
constexpr int32_t kShortPollMs = 200;
constexpr int32_t kLongPollMs = 7300;
// 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;
// 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
// 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;
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.
// 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) {
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 (size_t probe = 0; probe < kTimedProbesMs.size() && failure.empty(); ++probe) {
const TimedPoll poll = PollFor(runtime, kTimedProbesMs[probe]);
overshoot[probe].Record(poll, kTimedProbesMs[probe]);
failure = CheckTimedPoll(poll, kTimedProbesMs[probe], kGenerousLateToleranceMs);
}
cultmesh_quic_runtime_close(runtime);
return failure;
}
int PollTimeout(int iterations) {
std::array<Overshoot, 3> overshoot{};
for (int iteration = 0; iteration < iterations; ++iteration) {
const std::string failure = PollTimeoutOnce(overshoot);
if (!failure.empty())
return Fail("polltimeout iteration " + std::to_string(iteration) + ": " + failure);
}
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],
overshoot[2].worst_ms, kTimedProbesMs[2]);
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.
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;
}
// 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 {};
}
// 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
// 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;
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";
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;
}
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;
}
// 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
// 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;
// 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) {
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";
}
}
// 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);
}
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();
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;
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) {
std::string failure = PollHammerOnce(false, false);
if (!failure.empty())
return Fail("pollhammer iteration " + std::to_string(iteration) + " (clean runtime): " + failure);
failure = PollHammerOnce(true, false);
if (!failure.empty())
return Fail("pollhammer iteration " + std::to_string(iteration) +
" (after a refused call recorded an error): " + failure);
failure = PollHammerOnce(false, true);
if (!failure.empty())
return Fail("pollhammer iteration " + std::to_string(iteration) +
" (with a live connection, stream and listener): " + failure);
}
std::printf("pollhammer %dx: ok\n", iterations);
return 0;
}
// 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) {
std::fprintf(stderr,
"usage: %s closerace|holdclose|holdtimeout|polltimeout|pollbusy|pollhammer|zerotimeout|"
"payloadfit|latecall|waitseam [iterations] [pollers]\n",
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);
if (scenario == "polltimeout") return PollTimeout(iterations);
if (scenario == "pollbusy") return PollBusy(iterations);
if (scenario == "pollhammer") return PollHammer(iterations);
if (scenario == "zerotimeout") return ZeroTimeout(iterations);
if (scenario == "payloadfit") return PayloadFit(iterations);
if (scenario == "holdclose" || scenario == "latecall" || scenario == "holdtimeout" ||
scenario == "waitseam") {
#if defined(CULTMESH_QUIC_DEBUG_ASSERTS)
if (scenario == "latecall") return LateCall(iterations);
if (scenario == "holdtimeout") return HoldTimeout(iterations);
if (scenario == "waitseam") return WaitSeam(iterations);
return HoldClose(iterations, pollers);
#else
std::fprintf(stderr,
"%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;
}