Expand description
Shared ready-marker path format for the
ktstr-jemalloc-alloc-worker binary and the integration tests
that drive it.
§Worker → probe ready signaling mechanism (design)
The worker writes a pid-scoped file after its allocation +
black-box triple completes; the test body polls for that file
before launching the probe. Centralizing the path format here
keeps the worker (src/bin/jemalloc_alloc_worker.rs) and the
test (tests/jemalloc_probe_tests.rs) in sync — a rename
changes one place, not two.
Medium: a file on the shared /tmp. The path is
/tmp/ktstr-worker-ready-<pid> where <pid> is the worker’s
decimal pid (see worker_ready_marker_path / WORKER_READY_MARKER_PREFIX).
The worker issues std::fs::write(path, b"ready\n") on
ready; the test waits for that file via wait_for_worker_ready
in the sibling worker_ready_wait module — inotify-event-driven
(IN_CREATE | IN_MOVED_TO on the marker’s parent directory),
re-checking Path::exists on every wake, with a 10 ms sleep
fallback when inotify is unavailable. A test-only env override —
WORKER_READY_MARKER_OVERRIDE_ENV — replaces the default
pid-scoped path when set and non-empty; production callers
never set it.
Why a file-on-tmp rather than a pipe / unix socket / vsock / stdout-token?
- Minimal setup before the worker’s own allocation path.
The probe must observe the worker’s post-allocation heap
state, not any pre-signaling setup cost.
std::fs::writeagainst an existing tmp directory is three syscalls (openat+write+close) on already-hot kernel caches; a socket would addsocket+connect+sendtoagainst a daemon that would itself need to be provisioned by the harness. - Shared filesystem namespace without dedicated plumbing.
Both the worker and the test body run as subprocesses
inside the SAME
#[ktstr_test]guest VM.PayloadRun::spawncreates the worker viastd::process::Command, which inherits the parent’s (guest-side) filesystem namespace, so the two processes see the same guest-VM tmpfs/tmp. No host involvement, no bind-mount, no guest↔host bridge. A socket path would still require a dedicated in-VM dispatcher; vsock would require a cid allocation. - No process-of-write hard dependency. A pipe close or EOF on the worker’s stdout would also signal readiness, but any crashing worker would look the same — the file approach surfaces “worker reached the signaling point” distinctly from “worker died before signaling”.
§Dual-compilation constraint — MUST STAY STD-ONLY
This source file is compiled TWICE by the same cargo build:
once as ktstr::worker_ready (a lib-crate module) and once as the
worker bin’s own mod worker_ready via
#[path = "../worker_ready.rs"] (see
src/bin/jemalloc_alloc_worker.rs). The #[path] include is
deliberate: linking the entire ktstr library into a worker
process would pull thousands of unused symbols and perturb the
probe’s cross-process timing.
Consequences for this file:
- No
crate::…paths, nouse crate::…statements.crateresolves to two different crates (ktstr vs. the bin) on the two compilation paths; anything that names the other crate’s types breaks one of the two builds. The nested#[cfg(test)] mod tests { … }block at the bottom of this file usessuper::to reach items defined here —super::from a childmod testsresolves to this file’s own items, which exist identically under both compilation paths, so the tests do not divide lib vs. bin. Those tests pin the path format (the prefix literal, decimal-pid formatting, and the override-env / stderr / stdout prefixes).tests/jemalloc_alloc_worker_exit_codes.rsandtests/jemalloc_probe_signals_test.rsmerely consume the same items through thektstr::worker_ready::…public surface; they do not re-pin the path-format literal. - No ktstr-library types or modules. Only
stditems, language primitives, andcoretypes are safe. Anything that depends onPayloadHandle, scenarioCtx,anyhow, or any other lib-only item must live inktstr::worker_ready_wait(lib-only) — not here. - No external crate imports that only the lib or only the bin
has. Adding a non-std dependency requires a matching
Cargo.tomlstanza for both the lib and the bin; otherwise one build path fails to resolve the crate. - No
#[cfg(feature = "…")]that differs across the two crates. Feature gates evaluate per-crate, so a gate that’s satisfied for the lib but not the bin (or vice versa) will silently diverge the two compiled copies.
The wait_for_worker_ready helper lives in the sibling
ktstr::worker_ready_wait module because it needs
PayloadHandle and therefore depends on the rest of the
library.
§In-VM invariants
The ready-marker scheme relies on two properties that the
#[ktstr_test] VM environment supplies:
- Shared
/tmpbetween worker and reader. Both the worker and the test body run as subprocesses inside the same guest VM, spawned viaPayloadRun::spawn→std::process::Command. The child inherits the parent’s guest-side filesystem namespace, so both processes see the same guest-VM tmpfs/tmp. The marker is NOT transported over a socket / pipe: if a future refactor puts the worker in a distinct filesystem namespace (e.g. viaunshare --mountor a separate VM), the poll will always time out and the ready-signal must move to a different medium (unix socket,vsock, or a stdout-token parse). PayloadHandle::pid() == std::process::id()inside the guest. The test body readsPayloadHandle::pid()to learn the worker’s pid, which is the same pid the worker observes viagetpid()/std::process::id()inside the VM — single-namespace because the worker runs without a separate pid-namespace. Consumers that add a pid-namespace or run the worker under something likeunshare --fork --pidmust also translate the pid before constructing the path, or the reader polls a path that the writer never materialized.
Both properties are invariants the ktstr-jemalloc-alloc-worker
jemalloc_probe_tests.rspair depends on; breaking either without updating this module’s scheme produces silent poll timeouts rather than loud errors.
Constants§
- WORKER_
READY_ MARKER_ OVERRIDE_ ENV - Name of the test-only env var that overrides the pid-scoped
default path. When set and non-empty, the worker writes the
ready marker at the override path instead of
worker_ready_marker_path(pid); when unset (or empty) the default pid-scoped path applies. - WORKER_
READY_ MARKER_ PREFIX - Prefix for the pid-scoped ready-marker path. The final segment is the worker’s pid rendered as a decimal ASCII integer.
- WORKER_
STDERR_ PREFIX - Stderr line prefix the
ktstr-jemalloc-alloc-workerbinary prepends to every fail-fast diagnostic it emits (missing argv, bytes=0, thread self-check, procfs unreadable, ready-marker write fail). Exported as apub constso host-side integration tests can assert against the binary’s emitted literal without duplicating it — a rename or a typo on either side shows up here in one place instead of silently desynchronizing. - WORKER_
STDOUT_ READY_ PREFIX - Stdout “ready” breadcrumb the
ktstr-jemalloc-alloc-workerbinary prints once, immediately before entering its terminal loop — the default-modesleep(3600s)park loop or the--churnspawn+join loop — after its allocation +black_boxtriple has materialised. The full emitted line is{WORKER_STDOUT_READY_PREFIX} pid={pid} bytes={bytes}; this const carries only the prefix so host-side consumers that want to grep the worker’s captured stdout (or that fold worker stdout into a larger test log) can match against a single authoritative literal.
Functions§
- worker_
ready_ marker_ path - Construct the ready-marker path for a worker with the given pid.