diff --git a/native/GameCult.Mesh.Quic.Native/README.md b/native/GameCult.Mesh.Quic.Native/README.md index 60531975..4653f86c 100644 --- a/native/GameCult.Mesh.Quic.Native/README.md +++ b/native/GameCult.Mesh.Quic.Native/README.md @@ -102,6 +102,26 @@ shipped beside it resolves without being installed system-wide. A Linux host still needs MsQuic's own runtime dependencies present: `libssl3t64`, `libnuma1`, `libxdp1`, `libnl-route-3-200`. +The development loop for it is one committed image, +[`scripts/quic-native-linux-dev.Dockerfile`](../../scripts/quic-native-linux-dev.Dockerfile), +pinned to the same `debian:13` digest the release workflow builds in. It carries +the toolchain, MsQuic's runtime dependencies, `setarch`, and Node, because the +mutation harness that drives the runtime-lifetime scenarios is a Node script and +a container that cannot run it cannot check the bridge it just built: + + docker build -t cultlib-quic-native-dev -f scripts/quic-native-linux-dev.Dockerfile scripts + docker run --rm --security-opt seccomp=unconfined -v F:\Projects\CultLib:/src -w /src \ + cultlib-quic-native-dev bash -lc "scripts/build-quic-native.sh && node scripts/mutate-cultmesh.mjs native" + +`--security-opt seccomp=unconfined` is load-bearing, not caution. The +ThreadSanitizer configuration needs the process's address space where it expects +it, so `scripts/mutate-cultmesh.mjs` re-executes those runs under `setarch -R`, +which asks the kernel for `personality(ADDR_NO_RANDOMIZE)`. Docker's default +seccomp profile denies that call. Without it ThreadSanitizer dies before `main` +with "unexpected memory mapping" — a configuration that never starts, which a +mutation harness would otherwise read as every mutant being killed. The harness +stops with that diagnosis rather than reporting kills it did not earn. + Where an artifact was built is part of what it is. The Linux binary is linked against Debian 13's glibc, so a build from anywhere else is a different artifact whatever the file name says. diff --git a/native/GameCult.Mesh.Quic.Native/tests/cultmesh_quic_native_tests.cpp b/native/GameCult.Mesh.Quic.Native/tests/cultmesh_quic_native_tests.cpp index 36794d22..2b2fa3a3 100644 --- a/native/GameCult.Mesh.Quic.Native/tests/cultmesh_quic_native_tests.cpp +++ b/native/GameCult.Mesh.Quic.Native/tests/cultmesh_quic_native_tests.cpp @@ -224,8 +224,9 @@ std::string HoldCloseOnce(int pollers) { for (auto& thread : threads) thread.join(); if (peak != pollers) - return "the library counted a peak of " + std::to_string(peak) + " host call(s) inside it, not " + - std::to_string(pollers) + "; the blocking crossing is not inside a call scope"; + 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) + diff --git a/scripts/mutate-cultmesh.mjs b/scripts/mutate-cultmesh.mjs index c8cced18..c75a9041 100644 --- a/scripts/mutate-cultmesh.mjs +++ b/scripts/mutate-cultmesh.mjs @@ -39,10 +39,21 @@ // reported killed from a win32-x64 release run where both in fact survive, and // die deterministically only under linux-x64 AddressSanitizer. // -// This script tests the platform it is running on. For the linux-x64 native -// entries, run it inside the Debian 13 container the bridge is built in, with -// cmake, ninja, a C++20 compiler and Node present; a Windows run reports those -// entries as skipped and says so. +// This script tests the platform it is running on, and a run that skipped +// entries exits 2 rather than 0: those rules were covered by nothing. For the +// linux-x64 native entries, run it inside the committed development image, +// `scripts/quic-native-linux-dev.Dockerfile`, which is the same debian:13 digest +// the bridge is built in and carries Node, ninja, setarch and MsQuic's runtime +// dependencies: +// +// docker build -t cultlib-quic-native-dev -f scripts/quic-native-linux-dev.Dockerfile scripts +// docker run --rm --security-opt seccomp=unconfined -v F:\Projects\CultLib:/src -w /src \ +// cultlib-quic-native-dev bash -lc "node scripts/mutate-cultmesh.mjs native" +// +// The seccomp flag is required, not cautious: the ThreadSanitizer configuration +// is re-executed under `setarch -R`, and Docker's default profile denies the +// personality call that needs. See `explainUnrunnable` below, and the bridge's +// README. // // node scripts/mutate-cultmesh.mjs [target...] @@ -625,6 +636,10 @@ function nativeScenariosPass() { try { execFileSync("cmake", [ "-S", source, "-B", build, + // The same generator scripts/build-quic-native.sh uses. CMake's Linux + // default is Makefiles, and the container the bridge is built in carries + // ninja rather than make, so the default configured nothing at all. + ...(platform === "linux-x64" ? ["-G", "Ninja"] : []), "-DCMAKE_BUILD_TYPE=RelWithDebInfo", `-DCMAKE_CXX_FLAGS=${flags}`, `-DCMAKE_EXE_LINKER_FLAGS=${flags}`, diff --git a/scripts/quic-native-linux-dev.Dockerfile b/scripts/quic-native-linux-dev.Dockerfile new file mode 100644 index 00000000..7c796749 --- /dev/null +++ b/scripts/quic-native-linux-dev.Dockerfile @@ -0,0 +1,38 @@ +# The linux-x64 development loop for the CultMesh native QUIC bridge: build it, +# run its scenarios, and run the mutation harness over it. Not a release +# builder — the shipped Linux artifact comes from the workflow (see +# docs/typescript-quic-realtime-cut.md). +# +# This image is what the harness's linux-x64 entries are honest about. Before it +# was committed, the recipe in the cut map installed a C++ toolchain and no +# JavaScript runtime, so the container the bridge was built in could not run the +# runner that mutates it at all. +# +# docker build -t cultlib-quic-native-dev -f scripts/quic-native-linux-dev.Dockerfile scripts +# docker run --rm --security-opt seccomp=unconfined -v F:\Projects\CultLib:/src -w /src \ +# cultlib-quic-native-dev bash -lc "scripts/build-quic-native.sh && node scripts/mutate-cultmesh.mjs native" +# +# `--security-opt seccomp=unconfined` is not optional and not caution. The +# ThreadSanitizer configuration needs the process's address space where it +# expects it, so the harness re-executes it under `setarch -R`, which asks the +# kernel for personality(ADDR_NO_RANDOMIZE). Docker's default seccomp profile +# denies that call, and without it ThreadSanitizer dies before main with +# "unexpected memory mapping" — a whole configuration failing to start, which a +# mutation harness reads as every mutant being killed. +# +# Pinned by digest, not by the moving tag: the artifact is the toolchain that +# made it. +FROM debian:13@sha256:f324c7ff54321e8d9c588493a20244965938ce0aa50bbd1022d38010e9ffc4b1 + +# cmake, ninja and g++ build the bridge and its scenario runner; curl, +# ca-certificates and dpkg fetch and unpack the pinned libmsquic deb; +# util-linux carries setarch; nodejs runs scripts/mutate-cultmesh.mjs. The four +# libraries are MsQuic's own runtime dependencies, which the bridge does not +# vendor. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + bash ca-certificates cmake curl dpkg g++ ninja-build nodejs util-linux \ + libssl3t64 libnuma1 libxdp1 libnl-route-3-200 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /src