From 5c1e14545fd3c367db3851084bce949088ee1c0f Mon Sep 17 00:00:00 2001 From: Costa Tsaousis Date: Sun, 27 Sep 2026 17:11:21 +0300 Subject: [PATCH 1/3] Fix Linux SHM futex timeout ABI on time64 libc --- .../SOW-0036-20260927-shm-futex-time64-abi.md | 281 ++++++++++++++++++ ...37-20260927-linux-32-bit-build-coverage.md | 128 ++++++++ .github/workflows/runtime-safety.yml | 25 ++ CMakeLists.txt | 5 + bench/drivers/rust/src/main.rs | 5 +- docs/level1-posix-shm.md | 41 +++ docs/netipc-integrator-skill.md | 9 + src/crates/netipc/src/transport/shm.rs | 46 +-- src/crates/netipc/src/transport/shm_tests.rs | 14 +- src/crates/netipc/tests/shm_timeout.rs | 42 +++ .../pkg/netipc/transport/posix/shm_linux.go | 6 +- .../netipc/src/transport/posix/netipc_shm.c | 22 +- tests/fixtures/c/test_shm_timeout.c | 99 ++++++ tests/run-rust-shm-timeout-abi.sh | 17 ++ tests/run-shm-timeout-abi.sh | 23 ++ 15 files changed, 733 insertions(+), 30 deletions(-) create mode 100644 .agents/sow/done/SOW-0036-20260927-shm-futex-time64-abi.md create mode 100644 .agents/sow/pending/SOW-0037-20260927-linux-32-bit-build-coverage.md create mode 100644 src/crates/netipc/tests/shm_timeout.rs create mode 100644 tests/fixtures/c/test_shm_timeout.c create mode 100755 tests/run-rust-shm-timeout-abi.sh create mode 100755 tests/run-shm-timeout-abi.sh diff --git a/.agents/sow/done/SOW-0036-20260927-shm-futex-time64-abi.md b/.agents/sow/done/SOW-0036-20260927-shm-futex-time64-abi.md new file mode 100644 index 0000000..ce7c317 --- /dev/null +++ b/.agents/sow/done/SOW-0036-20260927-shm-futex-time64-abi.md @@ -0,0 +1,281 @@ +# SOW-0036 - Linux SHM futex timeout ABI portability + +## Status + +Status: completed + +Sub-state: source repair, local ABI validation, documentation and CI configuration completed; no downstream deployment. + +## Requirements + +### Purpose + +Preserve real blocking timeouts for idle SHM sessions on 32-bit time64 libc builds. + +### User Request + +The user supplied a corrected investigation identifying a libc/kernel timespec ABI +mismatch in the C futex wrapper, with immediate timeouts and idle session CPU use. +Treat this as an upstream repair; deployment and host configuration are outside scope. + +### Assistant Understanding + +Facts: C and Rust pass libc timespec pointers to SYS_futex. The receive timeout is +an unsigned 32-bit millisecond duration. Go uses syscall.Timespec but hardcodes +64-bit field assignments. Existing Rust timeout tests assert the error only. + +Inferences: explicit kernel ABI marshalling repairs the C and Rust failure without +changing the shared-memory layout or service polling policy. + +Unknowns: affected deployed binaries have not been independently traced in this +session; no production CPU reduction will be claimed from local tests. + +### Acceptance Criteria + +- Empty SHM receives actually wait for subsecond and multisecond budgets. +- Finite and infinite receives still wake when a peer sends. +- ARM time64 C regression fails before the fix and passes after it. +- Native C/Rust/Go SHM tests and cross-language interop remain passing. +- ABI policy and reproducible portability checks are documented. + +## Analysis + +Sources checked: docs/level1-posix-shm.md, docs/code-organization.md, C/Rust/Go SHM +sources, C/Rust tests, CMakeLists.txt, runtime-safety workflow, SOW-0006 and SOW-0016. +Pending/current SOWs concern scale, maintainability, downstream integration, platform +build gating, permissions, and Rust style; all current SOWs are paused. This is a +latent initial-implementation defect (C wrapper originates in f71db33), not a reversal +of a completed SOW's specific time64 fix. No SOW claims prior 32-bit time64 validation. +The local SOW specs directory contains only .gitkeep. The only runtime project skill +is downstream vendoring preflight; it does not apply to source-only repairs. + +Go 386 build inspection also found pre-existing UDS and test compilation errors; +track those separately rather than expanding this repair into a platform port. + +## Pre-Implementation Gate + +Status: ready + +Problem / root-cause model: + +- On 32-bit time64 libc, libc timespec seconds occupy eight bytes, while the + legacy futex ABI consumes kernel-sized seconds and nanoseconds. A subsecond + timeout can therefore become zero. Rust shares this pointer-layout assumption. + +Evidence reviewed: + +- Source wrappers and callers, existing timeout tests, public SHM synchronization + contract, local Linux UAPI types, and musl's __timedwait.c web source. +- User live observations are reported evidence, not independently reproduced here. + +Affected contracts and surfaces: + +- Linux C and Rust SHM waiting, Go relative-time conversion, regression fixtures, + portability test script, runtime CI, public SHM docs and integrator guide. +- Public APIs, wire layout, service polling intervals and Windows remain unchanged. + +Existing patterns to reuse: + +- Preserve monotonic deadlines, EINTR/EAGAIN retries, shared FUTEX_WAIT/WAKE, + low-priority test runner and existing SHM interop fixtures. + +Risk and blast radius: + +- Wrong ABI selection can busy-loop or hang all Linux SHM users. Keep legacy + syscall support for older kernels; validate timeout and wake paths. The maximum + relative timeout is UINT32_MAX milliseconds, whose seconds fit signed 32 bits, + so legacy futex marshalling suffices wherever that syscall exists. + +Sensitive data handling plan: + +- Do not persist hostnames, host paths, private endpoints, personal data or raw + investigation notes in SOWs, specs, docs, skills, agent instructions or comments. + Record only source references, synthetic test paths and sanitized measurements. + +Implementation plan: + +1. Add public receive timing/wake regression tests and reproduce on ARM time64. +2. Marshal kernel timeout fields in C and Rust; correct Go duration construction. +3. Add repeatable 32-bit regression command and CI coverage; update public guidance. +4. Run focused native, cross-ABI and interoperability validation, review changes, + and commit implementation and completed SOW together. + +Validation plan: + +- Real ARM Linux user ABI execution under QEMU with musl time64, plus native tests. +- Short and >1-second idle waits, delayed message wake, zero timeout wake, + CPU-versus-wall waiting evidence, maximum API timeout interrupted by a message. +- Rust stable and MSRV 1.91.0; C/Rust/Go SHM and service SHM interop. +- Search raw timed syscalls, review ABI/endian/null handling, diff check, SOW audit. + +Artifact impact plan: + +- AGENTS.md: no new responsibility or global workflow. +- Runtime project skills: source-only repair does not change downstream preflight. +- Specs: clarify ABI invariant in authoritative docs; avoid duplicate SOW spec. +- End-user/operator docs: describe portability check and its validation limits. +- End-user/operator skills: add 32-bit validation guidance to integrator guide. +- SOW lifecycle: new SOW for latent initial defect; track separate Go port issues. + +Open-source reference evidence: + +- Web reference: https://git.musl-libc.org/cgit/musl/tree/src/thread/__timedwait.c + (blob 666093be98516a1c84b2997f075a8dbfcb797b2c), explicitly marshals futex timeouts. + No external mirrored/cloned repository was used. + +Open decisions: + +- No product decision is needed: restore existing blocking semantics and retain + older-kernel compatibility. Do not disable cgroups or deploy changes. + +## Implications And Decisions + +Use the legacy futex syscall when available with its kernel layout, since every +supported relative timeout fits; time64-only targets require a two-int64 layout. +This avoids depending on new kernel support merely because libc uses time64. + +## Plan + +Follow the four gate implementation steps above, keeping other SOWs paused. + +## Execution Log + +### 2026-09-27 + +- Confirmed the unsafe libc pointer handoff in C and Rust. +- Confirmed Go 386 build failures in SHM duration construction, UDS Iovlen/sendmsg, + and oversized test literals. Broader UDS portability is tracked in SOW-0037. +- Reproduced C immediate expiry on ARM musl and i386 glibc time64. Native x86_64 + and i386 time32 pass the same fixture before the repair. +- Added explicit kernel-word marshalling in C and Rust, preserving null infinite + waits and legacy syscall use for representable relative durations. +- Rust time64 builds exposed private timespec padding: use Default initialization + in transport and the benchmark clock helper. The benchmark helper is built by + Cargo integration tests; no benchmark methodology or performance claim changed. +- Reproduced the old Rust wrapper under ARM musl time64 in a temporary standalone + crate with only the timespec initialization compatibility adjustments retained: + 100 ms returned in 0.894 ms. Repaired public-API test passes time32 and time64. +- Added C native CTest fixture, C cross-ABI script, Rust public-API integration + fixture and cross-ABI script, and Runtime Safety ARM CI job. +- User asked about libc wrappers and language scope. Confirmed that syscall does + not marshal timespec, musl helpers are internal, C/Rust share the issue, and + pure Go's syscall.Timespec does not have this libc/kernel mismatch. +- Full Rust ARM unit tests exposed a separate pthread_t Send test compilation + failure; tracked in SOW-0037. The standalone SHM public-API fixture runs without + that unrelated unit-test dependency. + +## Validation + +Acceptance criteria evidence: + +- C ARM musl time64 before: 100 ms returned in 0.278 ms; 1100 ms returned in + 1000.168 ms; delayed finite receive returned timeout. After: 100.308 ms and + 1100.151 ms, all finite/infinite/UINT32_MAX message wake checks passed. +- C i386 glibc time64 independently failed before and passed after. i386 time32 + and native x86_64 pass. Big-endian ARM musl time64 (nsec offset 12) also passes. +- Rust ARM musl time32 and time64 public API waits and delayed-message checks pass; + old wrapper with time64 returns in under 1 ms and fails the elapsed-time assertion. +- C idle test records less than 1 ms process CPU per 100 ms wait under ARM QEMU; + this is local blocking evidence, not a production performance estimate. + +Tests or equivalent validation: + +- Low-priority CMake configure/build and focused CTest: 7/7 passed (C SHM, + timeout ABI, C service, Rust SHM, Go SHM, SHM interop, service SHM interop). +- After Rust constructor updates: rebuilt and reran affected Rust SHM, C timeout + and both interop suites: 4/4 passed. Interop covers all nine C/Rust/Go pairings. +- Rust 1.91.0 and latest stable 1.98.1: 50 SHM unit tests and public-API + integration fixture passed on each. Latest stable also passed both ARM musl + time layouts and the Clippy correctness/suspicious gate. +- C cross-ABI commands: run-shm-timeout-abi.sh with native cc, cc -m32, cc -m32 + -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64, Zig ARM musl and Zig big-endian ARM musl. +- run-rust-shm-timeout-abi.sh: both ARM libc crate time configurations pass. +- Actionlint, ShellCheck, Rust format check, YAML parse and diff check pass. +- Clippy correctness/suspicious gate passes; existing advisory warnings remain. + Added a narrow documented allowance for field reassignment because the suggested + struct literal fails to compile with libc's private time64 padding. +- New GitHub job is configured but has not run remotely; local C glibc coverage + uses i386 and local ARM C coverage uses musl. No CI result is claimed. + +Real-use evidence: + +- Public SHM server/client mappings with no traffic actually block; forked C + peers and Rust thread peers wake finite/infinite/maximum-timeout receives. +- Production hosts and downstream source were not modified or remeasured. + +Reviewer findings: + +- Assistant self-review checked timeout width bounds, null pointers, shared futex + flags, x32 kernel word size, endian handling, Rust private padding, cleanup and + CI prerequisites. Corrected missing explicit cross-libc headers in the CI install + and ShellCheck's masked-command-status warning. No independent reviewer used. + +Same-failure scan: + +- Searched C/Rust/Go raw syscall and timespec use. Only C and Rust SHM wait wrappers + passed libc timespec to raw timed syscalls; both repaired. Rust benchmark clock + helper also needed Default construction for time64 compilation. Go uses kernel + Timespec; fixed its architecture-dependent duration construction. Remaining + independent 32-bit builds are represented by pending SOW-0037. + +Sensitive data gate: + +- Durable changes contain synthetic test paths and sanitized timing evidence, + not host identities, private endpoints, raw investigation notes, personal data + or credentials. Public musl source URL and source file paths are safe references. + +Artifact maintenance gate: + +- AGENTS.md: unchanged; responsibilities, protocol layers and low-priority workflow + are preserved. No new project-wide guardrail is required. +- Runtime project skills: unchanged; no downstream copy or preflight workflow changed. +- Specs: updated docs/level1-posix-shm.md with local ABI invariant; no duplicate + .agents/sow/specs document needed for an existing public contract. +- End-user/operator docs: docs/level1-posix-shm.md includes reproducible C/Rust + portability commands and explicitly limits emulator evidence. +- End-user/operator skills: docs/netipc-integrator-skill.md adds target-libc timeout + validation guidance; it does not claim complete 32-bit language support. +- SOW lifecycle: SOW-0036 closes with the implementation in the same commit; + SOW-0037 remains open/pending for independent platform build gaps. Existing + current SOWs remain paused. No archived TODO history changed. + +Specs update: authoritative SHM spec updated as above; wire layout unchanged. + +Project skills update: runtime vendoring skill unchanged because no vendoring occurs. + +End-user/operator docs update: public ABI requirement and test commands updated. + +End-user/operator skills update: integrator guide points consumers to target testing. + +Lessons: + +- A timeout error alone cannot prove real waiting. Check elapsed time and wakeup. +- libc time types are not raw syscall layouts; test both widths and endian variants. + +Follow-up mapping: + +- Timeout marshalling, tests, docs and CI: implemented in SOW-0036. +- Go UDS 32-bit and Rust pthread_t unit-test build issues: tracked in SOW-0037. +- Deployment, host configuration changes and CPU claims: outside the source-repair + scope; no promised production outcome is marked complete here. + +## Outcome + +C and Rust marshal the selected futex timeout ABI explicitly. Go constructs its +kernel Timespec portably. Subsecond/multisecond waiting and delayed-peer wakeup +are covered by real cross-ABI execution, native tests and CI configuration. +Public APIs, wire layout and service polling intervals are unchanged. Production +CPU reduction remains unmeasured because deployment was outside this task. + +## Lessons Extracted + +Timeout error assertions alone cannot distinguish real waiting from immediate expiry. + +## Followup + +Independent Go 32-bit UDS and Rust pthread_t unit-test build failures are tracked +in pending SOW-0037. No unrepresented deferred implementation remains. + +## Regression Log + +No prior completed time64 repair exists; this is a latent initial defect. diff --git a/.agents/sow/pending/SOW-0037-20260927-linux-32-bit-build-coverage.md b/.agents/sow/pending/SOW-0037-20260927-linux-32-bit-build-coverage.md new file mode 100644 index 0000000..85c9928 --- /dev/null +++ b/.agents/sow/pending/SOW-0037-20260927-linux-32-bit-build-coverage.md @@ -0,0 +1,128 @@ +# SOW-0037 - Linux 32-bit transport build coverage + +## Status + +Status: open + +Sub-state: separately tracked build gaps discovered during SOW-0036; no implementation started. + +## Requirements + +### Purpose + +Make Linux 32-bit language builds and test coverage match the supported platform contract. + +### User Request + +Follow-up from investigation of the reported SHM timeout ABI defect. The immediate +C/Rust futex repair is SOW-0036; independent platform build failures belong here. + +### Assistant Understanding + +Facts: Go 386 transport builds fail on UDS Iovlen width, missing SYS_SENDMSG and +oversized integer literals in tests. ARM musl Rust unit tests fail because a +pthread_t pointer captured by a thread closure is not Send. These are distinct +from the futex timeout marshalling defect. + +Inferences: target-aware syscall and test handling is needed before claiming +complete 32-bit Go/Rust transport coverage. + +Unknowns: full affected-target inventory and UDS syscall strategy remain to be +investigated in this SOW before implementation. + +### Acceptance Criteria + +- Define and document the tested Linux 32-bit target/libc matrix. +- Build and execute transport/service tests and interop on those targets. +- Preserve pure Go and the existing shared wire contract. + +## Analysis + +SOW-0036 command evidence: GOARCH=386 CGO_ENABLED=0 go test +./pkg/netipc/transport/posix fails at uds.go Iovlen and SYS_SENDMSG and +uds_more_edge_test.go oversized literals. cargo test --target +arm-unknown-linux-musleabihf --lib fails at raw_unix_tests.rs pthread_kill +thread closure. SOW-0036 corrects the SHM Timespec construction separately. + +## Pre-Implementation Gate + +Status: blocked + +Problem / root-cause model: architecture-width assumptions and libc-dependent +pthread_t types prevent cross-target compilation. + +Evidence reviewed: source errors from Go 386 transport tests and Rust ARM musl +unit tests, and the SOW-0036 public-API timeout fixture. + +Affected contracts and surfaces: Linux UDS syscall code, Rust/Go tests, CI, +platform documentation and integrator guidance. + +Existing patterns to reuse: architecture-specific Go build files, low-priority +runner, QEMU user execution, existing C/Rust/Go interop fixtures. + +Risk and blast radius: UDS sendmsg changes can affect all Go Linux transport +traffic; target matrix must be investigated before editing production code. + +Sensitive data handling plan: persist only synthetic test inputs and source +references in SOWs, specs, docs, skills, instructions and code comments. No +host inventories, secrets, private endpoints or personal data are needed. + +Implementation plan: + +1. Inventory failing Linux target builds and define the supported matrix. +2. Fill this gate with concrete syscall and test changes before activating. +3. Implement architecture-correct UDS and test behavior and validate interop. + +Validation plan: cross-target compile and runtime tests, syscall error paths, +C/Rust/Go interop, documentation review and SOW audit. + +Artifact impact plan: + +- AGENTS.md: update only if validated target commands become project-wide requirements. +- Runtime project skills: downstream preflight remains unchanged for source-only work. +- Specs: preserve wire formats; document any changed platform guarantees. +- End-user/operator docs: publish actual tested target matrix and commands. +- End-user/operator skills: reflect new target validation guidance. +- SOW lifecycle: pending follow-up to SOW-0036; complete independently. + +Open-source reference evidence: no external repository used in this initial +failure record; syscall ABI references must be checked during investigation. + +Open decisions: target matrix and syscall implementation are unresolved +investigation items, not permission to start an unbounded architecture port. + +## Implications And Decisions + +Keep independent build-portability failures out of the immediate timeout repair. + +## Plan + +Investigate and complete the pre-implementation gate before starting edits. + +## Execution Log + +### 2026-09-27 + +Recorded reproducible compile failures from SOW-0036. + +## Validation + +This pending SOW records compiler evidence only; implementation acceptance, +real-use testing, review, same-failure scan and artifact gates remain required +when activated. Status open agrees with pending directory. + +## Outcome + +Tracked; not implemented. + +## Lessons Extracted + +Native tests cannot prove architecture-dependent syscall types or pthread_t handling. + +## Followup + +SOW-0036 owns futex timeout conversion; this SOW owns the separate build gaps. + +## Regression Log + +No completed 32-bit build fix has been identified to reopen. diff --git a/.github/workflows/runtime-safety.yml b/.github/workflows/runtime-safety.yml index c59da22..061adc7 100644 --- a/.github/workflows/runtime-safety.yml +++ b/.github/workflows/runtime-safety.yml @@ -31,6 +31,31 @@ concurrency: cancel-in-progress: true jobs: + shm-timeout-abi: + name: ARM SHM timeout ABI + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + - name: Install ARM compiler and emulator + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc6-dev-armhf-cross qemu-user + - name: Check legacy and time64 libc layouts + env: + NIPC_TEST_RUNNER: qemu-arm + run: | + bash tests/run-shm-timeout-abi.sh arm-linux-gnueabihf-gcc -static + bash tests/run-shm-timeout-abi.sh arm-linux-gnueabihf-gcc -static -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64 -DNIPC_TEST_REQUIRE_TIME64_32 + + - name: Check Rust ARM musl legacy and time64 layouts + run: | + rustup target add arm-unknown-linux-musleabihf + bash tests/run-rust-shm-timeout-abi.sh + asan-ubsan: name: ASAN/UBSAN runs-on: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index 992a222..5cf0006 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -310,6 +310,11 @@ if(NOT NETIPC_WINDOWS_RUNTIME AND NOT APPLE) add_test(NAME test_shm COMMAND test_shm) set_tests_properties(test_shm PROPERTIES TIMEOUT 30) + add_executable(test_shm_timeout tests/fixtures/c/test_shm_timeout.c) + target_link_libraries(test_shm_timeout PRIVATE netipc_shm) + add_test(NAME test_shm_timeout COMMAND test_shm_timeout) + set_tests_properties(test_shm_timeout PROPERTIES TIMEOUT 25) + # C SHM interop binary add_executable(interop_shm_c tests/fixtures/c/interop_shm.c) target_link_libraries(interop_shm_c PRIVATE netipc_shm netipc_protocol) diff --git a/bench/drivers/rust/src/main.rs b/bench/drivers/rust/src/main.rs index 8afd181..da4f200 100644 --- a/bench/drivers/rust/src/main.rs +++ b/bench/drivers/rust/src/main.rs @@ -59,10 +59,7 @@ mod posix_only { // --------------------------------------------------------------------------- fn cpu_ns() -> u64 { - let mut ts = libc::timespec { - tv_sec: 0, - tv_nsec: 0, - }; + let mut ts = libc::timespec::default(); unsafe { libc::clock_gettime(libc::CLOCK_PROCESS_CPUTIME_ID, &mut ts); } diff --git a/docs/level1-posix-shm.md b/docs/level1-posix-shm.md index 5604ae6..34eb063 100644 --- a/docs/level1-posix-shm.md +++ b/docs/level1-posix-shm.md @@ -175,6 +175,16 @@ kernel-assisted blocking: 3. The publisher always calls `futex(FUTEX_WAKE)` after advancing the sequence, regardless of whether the consumer is spinning or waiting. +Futex timeout arguments must use the selected Linux syscall's ABI, not assume +that libc's `struct timespec` has the same layout. In particular, 32-bit libc +may use 64-bit seconds while the legacy syscall expects two 32-bit fields. +All receive budgets are unsigned 32-bit milliseconds: their relative seconds +fit in the legacy field, so implementations can retain the legacy syscall on +older kernels by explicitly marshalling its fields. Time64-only syscall ABIs +require two 64-bit fields. Timeout zero still means an infinite wait (a null +timeout pointer). This is local syscall marshalling; the shared region layout +and cross-language wire contract are unchanged. + The spin count is a performance tuning parameter. The default of 128 balances throughput against CPU usage on production VMs. Higher values increase maximum throughput but also increase CPU consumption at low @@ -321,3 +331,34 @@ stale detection logic to the target path before attempting `O_EXCL` create. If a stale file exists and `{run_dir}` is safe, it is unlinked first. If a live file exists, or `{run_dir}` is unsafe for automatic stale unlink, the create fails with address-in-use. + +## Timeout ABI regression validation + +Run `bash tests/run-shm-timeout-abi.sh` for the native C ABI. To check a 32-bit +ARM build with time64 libc, install an ARM GNU cross compiler and QEMU user +emulation, then run: + +```sh +NIPC_TEST_RUNNER=qemu-arm bash tests/run-shm-timeout-abi.sh \ + arm-linux-gnueabihf-gcc -static -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64 \ + -DNIPC_TEST_REQUIRE_TIME64_32 +``` + +For an installed Zig toolchain with its bundled musl headers/libraries: + +```sh +NIPC_TEST_RUNNER=qemu-arm bash tests/run-shm-timeout-abi.sh \ + zig cc -target arm-linux-musleabihf -mcpu=arm1176jzf_s -static \ + -DNIPC_TEST_REQUIRE_TIME64_32 +``` + +The fixture checks elapsed time and CPU time for empty receives, including +subsecond and multisecond budgets, and message wakeup with finite, infinite, +and maximum API timeouts. For Rust, install `qemu-arm` and the rustup target +`arm-unknown-linux-musleabihf`, then run +`bash tests/run-rust-shm-timeout-abi.sh`. This runs the public-API fixture with +both libc crate musl time layouts, using its `RUST_LIBC_UNSTABLE_MUSL_V1_2_3` +test configuration for time64. Runtime Safety CI checks both C ARM glibc +layouts and both Rust ARM musl layouts. Emulation validates syscall ABI +behavior; it does not establish production CPU usage or full C/Rust/Go support +for that architecture. diff --git a/docs/netipc-integrator-skill.md b/docs/netipc-integrator-skill.md index 8026ba5..8b08494 100644 --- a/docs/netipc-integrator-skill.md +++ b/docs/netipc-integrator-skill.md @@ -27,6 +27,15 @@ authoritative: - [docs/codec-cgroups-lookup.md](codec-cgroups-lookup.md) - [docs/codec-apps-lookup.md](codec-apps-lookup.md) +## Linux 32-bit timeout validation + +When integrating on 32-bit Linux, run the target-libc timeout regression in +[POSIX SHM validation](level1-posix-shm.md#timeout-abi-regression-validation). +A timeout error alone does not prove that the thread blocked for its budget. +Verify elapsed waiting and delayed-peer wakeup, especially with time64 libc. +The C ABI fixture does not replace building and testing each language consumer +for the target architecture or measuring the deployed service's CPU use. + ## Core Reality Before adding anything, internalize these facts: diff --git a/src/crates/netipc/src/transport/shm.rs b/src/crates/netipc/src/transport/shm.rs index 2cce96a..f6cdf7c 100644 --- a/src/crates/netipc/src/transport/shm.rs +++ b/src/crates/netipc/src/transport/shm.rs @@ -595,10 +595,7 @@ impl ShmContext { // timeout_ms regardless of retries. if !observed { let deadline_ns: u64 = if timeout_ms > 0 { - let mut ts = libc::timespec { - tv_sec: 0, - tv_nsec: 0, - }; + let mut ts = libc::timespec::default(); unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) }; ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64 + timeout_ms as u64 * 1_000_000 } else { @@ -613,22 +610,21 @@ impl ShmContext { break; // response arrived } - // Compute remaining timeout for this futex_wait call + // Compute remaining timeout for this futex_wait call. + // Time64 libc can have private padding, preventing struct literals. + #[allow(clippy::field_reassign_with_default)] let timeout = if deadline_ns > 0 { - let mut now_ts = libc::timespec { - tv_sec: 0, - tv_nsec: 0, - }; + let mut now_ts = libc::timespec::default(); unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut now_ts) }; let now_val = now_ts.tv_sec as u64 * 1_000_000_000 + now_ts.tv_nsec as u64; if now_val >= deadline_ns { return Err(ShmError::Timeout); } let remain = deadline_ns - now_val; - Some(libc::timespec { - tv_sec: (remain / 1_000_000_000) as libc::time_t, - tv_nsec: (remain % 1_000_000_000) as libc::c_long, - }) + let mut relative = libc::timespec::default(); + relative.tv_sec = (remain / 1_000_000_000) as _; + relative.tv_nsec = (remain % 1_000_000_000) as _; + Some(relative) } else { None }; @@ -1035,10 +1031,26 @@ fn futex_wake(addr: *mut u32, count: i32) -> i32 { } fn futex_wait(addr: *mut u32, expected: u32, timeout: Option<&libc::timespec>) -> i32 { - let tsp = match timeout { - Some(ts) => ts as *const libc::timespec, - None => ptr::null(), - }; + // SYS_futex consumes kernel words, not libc's timespec. In particular, + // 32-bit musl uses time64 even when SYS_futex is the legacy syscall. + // x32 uses 64-bit kernel words; riscv32 musl aliases SYS_futex to time64. + // Every u32 millisecond receive budget fits signed 32-bit relative seconds. + #[cfg(any( + target_arch = "x86_64", + all(target_arch = "riscv32", target_env = "musl") + ))] + type KernelTimeWord = i64; + #[cfg(not(any( + target_arch = "x86_64", + all(target_arch = "riscv32", target_env = "musl") + )))] + type KernelTimeWord = libc::c_long; + + let kernel_timeout = + timeout.map(|ts| [ts.tv_sec as KernelTimeWord, ts.tv_nsec as KernelTimeWord]); + let tsp = kernel_timeout + .as_ref() + .map_or(ptr::null(), |ts| ts.as_ptr()); unsafe { libc::syscall( libc::SYS_futex, diff --git a/src/crates/netipc/src/transport/shm_tests.rs b/src/crates/netipc/src/transport/shm_tests.rs index 0119a5c..abc8be7 100644 --- a/src/crates/netipc/src/transport/shm_tests.rs +++ b/src/crates/netipc/src/transport/shm_tests.rs @@ -878,8 +878,18 @@ fn test_receive_timeout() { // No client sends anything, so receive must timeout let mut buf = [0u8; 1024]; - let result = server.receive(&mut buf, 50); // 50ms timeout - assert_eq!(result.unwrap_err(), ShmError::Timeout); + server.spin_tries = 0; + for timeout_ms in [100, 1100] { + let start = std::time::Instant::now(); + let result = server.receive(&mut buf, timeout_ms); + let elapsed = start.elapsed(); + assert_eq!(result.unwrap_err(), ShmError::Timeout); + assert!( + elapsed >= Duration::from_millis(timeout_ms as u64), + "{elapsed:?}" + ); + assert!(elapsed < Duration::from_secs(6), "{elapsed:?}"); + } server.destroy(); cleanup_shm(svc, sid); diff --git a/src/crates/netipc/tests/shm_timeout.rs b/src/crates/netipc/tests/shm_timeout.rs new file mode 100644 index 0000000..d249ab8 --- /dev/null +++ b/src/crates/netipc/tests/shm_timeout.rs @@ -0,0 +1,42 @@ +#![cfg(target_os = "linux")] + +use netipc::transport::shm::{ShmContext, ShmError}; +use std::time::{Duration, Instant}; + +#[test] +fn shm_timeout_abi() { + let dir = std::env::temp_dir().join(format!("nipc-rust-timeout-{}", std::process::id())); + std::fs::create_dir(&dir).unwrap(); + let path = dir.to_str().unwrap(); + let mut server = ShmContext::server_create(path, "timeout", 1, 1024, 1024).unwrap(); + let mut client = ShmContext::client_attach(path, "timeout", 1).unwrap(); + let mut buf = [0u8; 64]; + println!( + "ABI: pointer={} time_t={} timespec={}", + std::mem::size_of::(), + std::mem::size_of_val(&libc::timespec::default().tv_sec), + std::mem::size_of::() + ); + for timeout in [100, 1100] { + let start = Instant::now(); + assert_eq!(server.receive(&mut buf, timeout), Err(ShmError::Timeout)); + let elapsed = start.elapsed(); + println!("idle {timeout} ms: {elapsed:?}"); + assert!(elapsed >= Duration::from_millis(timeout as u64)); + assert!(elapsed < Duration::from_secs(6)); + } + for timeout in [1000, 0, u32::MAX] { + let dir = dir.clone(); + let sender = std::thread::spawn(move || { + let mut peer = ShmContext::client_attach(dir.to_str().unwrap(), "timeout", 1).unwrap(); + std::thread::sleep(Duration::from_millis(50)); + peer.send(b"delayed peer message").unwrap(); + }); + let len = server.receive(&mut buf, timeout).unwrap(); + assert_eq!(&buf[..len], b"delayed peer message"); + sender.join().unwrap(); + } + client.close(); + server.destroy(); + std::fs::remove_dir(dir).unwrap(); +} diff --git a/src/go/pkg/netipc/transport/posix/shm_linux.go b/src/go/pkg/netipc/transport/posix/shm_linux.go index 725067a..235fac1 100644 --- a/src/go/pkg/netipc/transport/posix/shm_linux.go +++ b/src/go/pkg/netipc/transport/posix/shm_linux.go @@ -574,10 +574,8 @@ func (c *ShmContext) ShmReceive(buf []byte, timeoutMs uint32) (int, error) { return 0, ErrShmTimeout } remain := deadlineNs - nowVal - ts = &syscall.Timespec{ - Sec: int64(remain / 1_000_000_000), - Nsec: int64(remain % 1_000_000_000), - } + remaining := syscall.NsecToTimespec(int64(remain)) + ts = &remaining } ret := futexWaitCall(c.data, sigOff, sigVal, ts) diff --git a/src/libnetdata/netipc/src/transport/posix/netipc_shm.c b/src/libnetdata/netipc/src/transport/posix/netipc_shm.c index f307f5b..067a2b3 100644 --- a/src/libnetdata/netipc/src/transport/posix/netipc_shm.c +++ b/src/libnetdata/netipc/src/transport/posix/netipc_shm.c @@ -137,16 +137,32 @@ static int open_run_dir_fd(const char *run_dir) return fd; } -/* Thin wrapper around the futex syscall. */ +/* Use the kernel ABI, not libc's timespec (which can use time64 on a + * legacy 32-bit syscall ABI). All receive budgets are uint32_t milliseconds, + * so their relative seconds fit even the legacy signed 32-bit field. + * __kernel_long_t also preserves the 64-bit syscall words on x32. */ +#if defined(SYS_futex_time64) && (!defined(SYS_futex) || SYS_futex == SYS_futex_time64) +#define NIPC_SYS_FUTEX SYS_futex_time64 +typedef int64_t nipc_futex_time_word_t; +#else +#define NIPC_SYS_FUTEX SYS_futex +typedef __kernel_long_t nipc_futex_time_word_t; +#endif static int futex_wake(uint32_t *addr, int count) { - return (int)syscall(SYS_futex, addr, FUTEX_WAKE, count, NULL, NULL, 0); + return (int)syscall(NIPC_SYS_FUTEX, addr, FUTEX_WAKE, count, NULL, NULL, 0); } static int futex_wait(uint32_t *addr, uint32_t expected, const struct timespec *timeout) { - return (int)syscall(SYS_futex, addr, FUTEX_WAIT, expected, timeout, NULL, 0); + nipc_futex_time_word_t kernel_timeout[2]; + if (timeout) { + kernel_timeout[0] = (nipc_futex_time_word_t)timeout->tv_sec; + kernel_timeout[1] = (nipc_futex_time_word_t)timeout->tv_nsec; + } + return (int)syscall(NIPC_SYS_FUTEX, addr, FUTEX_WAIT, expected, + timeout ? kernel_timeout : NULL, NULL, 0); } /* CPU pause hint for spin loops. */ diff --git a/tests/fixtures/c/test_shm_timeout.c b/tests/fixtures/c/test_shm_timeout.c new file mode 100644 index 0000000..dc0063b --- /dev/null +++ b/tests/fixtures/c/test_shm_timeout.c @@ -0,0 +1,99 @@ +/* Exercise the public SHM wait path on native and cross-compiled Linux ABIs. */ +#define _GNU_SOURCE +#include "netipc/netipc_shm.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef NIPC_TEST_REQUIRE_TIME64_32 +_Static_assert(sizeof(void *) == 4, "test must exercise a 32-bit ABI"); +_Static_assert(sizeof(time_t) == 8, "test must exercise time64 libc"); +#endif + +static uint64_t clock_ns(clockid_t clock) +{ + struct timespec ts; + if (clock_gettime(clock, &ts) != 0) + abort(); + return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; +} + +static int idle_wait(nipc_shm_ctx_t *receiver, uint32_t timeout_ms) +{ + char buf[32]; + size_t len = 0; + uint64_t cpu = clock_ns(CLOCK_PROCESS_CPUTIME_ID); + uint64_t start = clock_ns(CLOCK_MONOTONIC); + nipc_shm_error_t err = nipc_shm_receive(receiver, buf, sizeof(buf), &len, timeout_ms); + uint64_t elapsed = clock_ns(CLOCK_MONOTONIC) - start; + cpu = clock_ns(CLOCK_PROCESS_CPUTIME_ID) - cpu; + printf("idle %u ms: elapsed %.3f ms, CPU %.3f ms, result %d\n", + timeout_ms, elapsed / 1e6, cpu / 1e6, err); + /* Loose upper/CPU bounds tolerate slow CI and emulation; lower bound catches + * immediate expiry and loss of the subsecond part of a longer timeout. */ + return err == NIPC_SHM_ERR_TIMEOUT && + elapsed >= (uint64_t)timeout_ms * 1000000ULL && + elapsed < (uint64_t)(timeout_ms + 5000) * 1000000ULL && + cpu < elapsed / 2; +} + +static int wake_wait(nipc_shm_ctx_t *receiver, nipc_shm_ctx_t *sender, + uint32_t timeout_ms) +{ + const char message[] = "delayed peer message"; + pid_t pid = fork(); + if (pid < 0) + return 0; + if (pid == 0) { + usleep(50000); + _exit(nipc_shm_send(sender, message, sizeof(message)) == NIPC_SHM_OK ? 0 : 1); + } + char buf[64]; + size_t len = 0; + nipc_shm_error_t err = nipc_shm_receive(receiver, buf, sizeof(buf), &len, timeout_ms); + int status = 0; + int waited = waitpid(pid, &status, 0) == pid; + printf("wake %u ms: result %d, length %zu\n", timeout_ms, err, len); + return waited && WIFEXITED(status) && WEXITSTATUS(status) == 0 && + err == NIPC_SHM_OK && len == sizeof(message) && + memcmp(buf, message, sizeof(message)) == 0; +} + +int main(void) +{ + /* Also bounds an accidentally infinite wait when run directly. */ + alarm(20); + printf("ABI: pointer=%zu time_t=%zu timespec=%zu nsec_offset=%zu\n", + sizeof(void *), sizeof(time_t), sizeof(struct timespec), + offsetof(struct timespec, tv_nsec)); + char dir[] = "/tmp/nipc_timeout_XXXXXX"; + if (!mkdtemp(dir)) + return 1; + nipc_shm_ctx_t server, client; + if (nipc_shm_server_create(dir, "timeout", 1, 1024, 1024, &server) != NIPC_SHM_OK) { + rmdir(dir); + return 1; + } + if (nipc_shm_client_attach(dir, "timeout", 1, &client) != NIPC_SHM_OK) { + nipc_shm_destroy(&server); + rmdir(dir); + return 1; + } + server.spin_tries = client.spin_tries = 0; + int ok = idle_wait(&server, 100); + ok &= idle_wait(&client, 100); + ok &= idle_wait(&server, 1100); + ok &= wake_wait(&server, &client, 1000); + ok &= wake_wait(&server, &client, 0); + ok &= wake_wait(&server, &client, UINT32_MAX); + nipc_shm_close(&client); + nipc_shm_destroy(&server); + rmdir(dir); + puts(ok ? "PASS: SHM timeout ABI" : "FAIL: SHM timeout ABI"); + return ok ? 0 : 1; +} diff --git a/tests/run-rust-shm-timeout-abi.sh b/tests/run-rust-shm-timeout-abi.sh new file mode 100755 index 0000000..429e2cb --- /dev/null +++ b/tests/run-rust-shm-timeout-abi.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Requires rustup target add arm-unknown-linux-musleabihf and qemu-arm. +set -euo pipefail +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/run-low-priority.sh" +netipc_low_priority_self "$@" +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +rust_host=$(rustc -vV | sed -n 's/^host: //p') +rust_sysroot=$(rustc --print sysroot) +export CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_LINKER="$rust_sysroot/lib/rustlib/$rust_host/bin/rust-lld" +export CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_RUSTFLAGS='-C linker-flavor=ld.lld' +export CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_RUNNER='timeout 25 qemu-arm' +for time64 in 0 1; do + RUST_LIBC_UNSTABLE_MUSL_V1_2_3=$time64 cargo test \ + --manifest-path "$root/src/crates/netipc/Cargo.toml" \ + --target arm-unknown-linux-musleabihf --test shm_timeout -- --nocapture +done diff --git a/tests/run-shm-timeout-abi.sh b/tests/run-shm-timeout-abi.sh new file mode 100755 index 0000000..e4ffebb --- /dev/null +++ b/tests/run-shm-timeout-abi.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Compile just the production SHM transport and its public wait regression. +# Pass the compiler and its flags as arguments; NIPC_TEST_RUNNER may name QEMU. +set -euo pipefail +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/run-low-priority.sh" +netipc_low_priority_self "$@" +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/nipc-timeout-abi.XXXXXX") +trap 'rm -rf "$build_dir"' EXIT +if (( $# == 0 )); then + set -- cc +fi +"$@" -O2 -Wall -Wextra -Werror \ + -I "$root/src/libnetdata/netipc/include" \ + "$root/tests/fixtures/c/test_shm_timeout.c" \ + "$root/src/libnetdata/netipc/src/transport/posix/netipc_shm.c" \ + -o "$build_dir/test_shm_timeout" +if [[ -n "${NIPC_TEST_RUNNER:-}" ]]; then + "$NIPC_TEST_RUNNER" "$build_dir/test_shm_timeout" +else + "$build_dir/test_shm_timeout" +fi From ae09c7c6639cff8e6ba8f71d1be1c1a7410ef09c Mon Sep 17 00:00:00 2001 From: Costa Tsaousis Date: Sun, 27 Sep 2026 17:38:08 +0300 Subject: [PATCH 2/3] Record validated time64 source and Netdata PR delivery --- ...0038-20260927-publish-vendor-shm-time64.md | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 .agents/sow/done/SOW-0038-20260927-publish-vendor-shm-time64.md diff --git a/.agents/sow/done/SOW-0038-20260927-publish-vendor-shm-time64.md b/.agents/sow/done/SOW-0038-20260927-publish-vendor-shm-time64.md new file mode 100644 index 0000000..f99113e --- /dev/null +++ b/.agents/sow/done/SOW-0038-20260927-publish-vendor-shm-time64.md @@ -0,0 +1,246 @@ +# SOW-0038 - Publish and vendor the SHM time64 repair + +## Status + +Status: completed + +Sub-state: both authorized PRs published; source preflight and targeted downstream validation passed. Merging and deployment are outside scope. + +## Requirements + +### Purpose +Publish the time64 SHM repair in plugin-ipc and Netdata through pull requests. + +### User Request +Create a Netdata git worktree under ~/src/PRs from the existing checkout, re-vendor +plugin-ipc, and open a Netdata PR. Also open a plugin-ipc PR because main is protected. +The request explicitly authorizes the worktree, branches, pushes and PR publication. + +### Acceptance Criteria +- Source PR contains the validated repair from SOW-0036. +- Vendoring starts only after source CI/scanner and two-way drift gates pass. +- Netdata PR preserves wrappers, workspace packaging and normalized imports. +- Targeted downstream builds/tests and source equality checks pass. +- Both PR URLs and actual CI status are reported; merging is outside this request. + +## Analysis + +The current source commit is 5c1e145; origin/main is its parent 0d17a9d. +GitHub reports main protected. Prior source and Netdata baseline is plugin-ipc +37cce82d4b0e1e9d1fbee2ff2ab561cc9920ffa6 and Netdata +d5796cb0a841adaabdd68d427664155d8e30d46e (SOW-0030/0033 evidence). +Netdata post-baseline source changes are the five Rust compatibility edits already +backported by SOW-0033; Cargo.toml workspace changes are downstream-owned. +Paused SOW-0027 covers the earlier memory-safety delivery, not this new time64 repair. +Other current/pending SOWs do not own this delivery; no other SOW is activated. + +## Pre-Implementation Gate + +Status: ready + +Problem / root-cause model: +- The source repair is committed locally but neither published for protected-main review + nor included in Netdata. Direct downstream patching would lose source ownership. + +Evidence reviewed: +- SOW-0036 validation, SOW-0030/0033 vendor baseline, source and Netdata histories, + vendor/diff scripts, project-netdata-vendoring skill, public SHM spec and integrator guide. + +Affected contracts and surfaces: +- Git branches, PRs, vendored C/Rust/Go SHM code, existing Netdata build integration. + +Existing patterns to reuse: +- Source-first PR and CI, project vendor/diff scripts, Go module normalization, + low-priority validation, downstream-owned wrappers and workspace manifests. + +Risk and blast radius: +- Overwriting downstream changes or publishing unvalidated code. Reconstruct and + classify drift before copying; require source GitHub checks/scanners to pass. + +Sensitive data handling plan: +- Store only source identifiers, sanitized checks and public PR links in SOWs, + specs, docs, skills, instructions and comments. Do not copy local host notes, + credentials, personal data, private endpoints or raw secret-scanning content. + +Implementation plan: +1. Publish 5c1e145 on a source topic branch and open its PR. +2. Create the user-authorized Netdata worktree; inspect applicable instructions, + local SOWs/specs, build rules and drift while source CI runs. +3. Record exact source checks, scanner findings, baseline and file migration plan; + fix source blockers before any vendor write. +4. Vendor, normalize imports, validate downstream, commit and open the Netdata PR. +5. Record actual PR/check outcomes and complete this delivery SOW with one commit. + +Validation plan: +- Source Actions/check runs/status and code/Dependabot/secret scanning. +- Exact baseline reconstruction and bidirectional file comparison. +- Downstream C production compile and public timeout fixture, Rust and Go tests, + normalized post-vendor diff and explicit wrapper/package preservation. +- Self-review of deterministic vendor diff; obtain independent review if material + integration uncertainty remains after these checks. +- SOW audit, sensitive-data review and git diff --check. + +Artifact impact plan: +- AGENTS.md: no workflow change; comply with branch protection and worktree authorization. +- Runtime skills: existing preflight governs; update only for demonstrated workflow gaps. +- Specs: source SHM ABI contract is already updated in SOW-0036. +- End-user/operator docs: preserve source instructions and reference them from PR evidence. +- End-user/operator skills: source integrator guide already captures time64 validation. +- SOW lifecycle: new delivery SOW; source repair SOW remains completed; downstream + local SOW follows Netdata's ignored-queue convention. + +Open-source reference evidence: +- netdata/plugin-ipc @ 5c1e145, docs/level1-posix-shm.md and vendor scripts. +- netdata/netdata @ 4464a9ed71c8a7351b540ce4ca207e1418faffea, + src/libnetdata/netipc, src/crates/netipc, src/go/pkg/netipc. + +Open decisions: +- None. The user fixed the delivery target and authorized PR publication. Source + failures must be repaired or reported; no risk waiver is presumed. + +## Implications And Decisions + +Use a source topic branch and a fresh Netdata branch off its current master. +The mandatory source preflight is a condition of vendoring, not a new permission request. + +## Plan + +Follow the five implementation steps in the gate. Do not merge either PR. + +## Execution Log + +### 2026-09-27 +- Confirmed protected plugin-ipc main and the exact historical vendor baseline. + +## Validation + +Acceptance criteria evidence: +- plugin-ipc PR: https://github.com/netdata/plugin-ipc/pull/17 +- Netdata PR: https://github.com/netdata/netdata/pull/24049 +- Netdata commit e73dd3903357f0a2f9825247f0fd284111bcaea2 exports source SDK + commit 5c1e14545fd3c367db3851084bce949088ee1c0f. The delivery-log commit changes + only this SOW; it does not alter the source files checked and vendored. +- User-authorized Netdata worktree uses branch fix/netipc-shm-time64 under the + requested PRs directory. Source PR uses fix/shm-futex-time64-abi. +- Source preflight details and exact baseline/migration plan are recorded below. + +Tests or equivalent validation: +- All eight source workflows succeeded before vendoring; scans have zero open alerts. +- Netdata CMake netipc target built; public SHM fixture passed against its archive. +- Netdata 32-bit glibc time64 reproducer failed before the copy (100 ms returned in + about 0.06 ms) and passed after (100.06 ms), including finite/infinite/max wakeup. +- Netdata Rust workspace: 50 SHM unit tests and standalone public test passed. +- Netdata Go: all pkg/netipc tests passed, including 110-second raw-service suite; + go vet and changed-file gofmt validation passed. +- Post-vendor normalized C/Rust/Go comparison reports no differences. All seven + downstream wrapper/package files retain their hashes. Exactly five source/test + files are committed downstream; no SOW/spec memory or build configuration is staged. +- git diff --check and source SOW audit pass. +- Netdata local SOW passes all its structural checks and the durable sensitive-data + scan passes. Whole-queue audit has one pre-existing invalid status in an unrelated + local netflow SOW plus advisory legacy gaps; no unrelated local memory is repaired. + +Real-use evidence: +- Real public SHM contexts, idle receives and delayed peer messages executed against + the downstream implementation. No production service was changed or remeasured. + +Reviewer findings: +- Main-agent self-review is sufficient for exact deterministic vendor import with + upstream ABI proof, downstream compile/runtime tests and preserved wrappers. + Checked clean target vs final five-file diff: no unrelated or unresolved drift. +- Source review identified the standalone Rust fixture omitted by src-only vendor + sync; copied it byte-for-byte. No downstream patch or local protocol fork exists. + +Same-failure scan: +- Source-owned C/Rust timed futex wrappers are updated together. Go remains pure Go. + No other Netdata NetIPC syscall wrapper or consumer API needed modification. + +Sensitive data gate: +- Records contain public source/check/PR identifiers and synthetic test evidence. + No private host names, endpoints, investigation notes, credentials or personal + data were written to committed artifacts. + +Artifact maintenance gate: +- AGENTS.md: unchanged; authorized worktree and protected-branch PR workflows followed. +- Runtime project skills: existing vendoring preflight fully applied; no policy change. +- Specs: source authoritative SHM ABI requirement already shipped in SOW-0036; + this import introduces no different API/wire guarantee requiring another spec. +- End-user/operator docs: upstream documentation already covers the ABI and tests; + downstream PR carries exact source and validation links, with no configuration change. +- End-user/operator skills: upstream integrator guide already updated; no new + downstream operator action or deployment procedure introduced. +- SOW lifecycle: this source delivery SOW completed and moved with its evidence commit; + Netdata local SOW remains ignored and records the PR's pending remote review/checks. + Earlier paused source SOWs and pending SOW-0037 remain unchanged. + +Specs update: no new contract; authoritative upstream SHM spec already current. +Project skills update: no new runtime workflow; explicit fixture copy recorded in +migration plan and PR, preserving complete normalized source equality. +End-user/operator docs update: PR explains symptoms, fix, reproduction and validation. +End-user/operator skills update: existing source integration guidance remains correct. +Lessons: verify copied test fixtures as well as src trees when the vendor checker +compares a complete crate. Source checks can pass before opening a downstream PR. +Follow-up mapping: all requested publication/vendoring implemented. Merge, deployment +and production remeasurement were not requested. No delivery item is deferred. + +## Outcome + +Published source PR #17 and Netdata PR #24049 without pushing to protected main. +Netdata's SDK copy exactly matches the validated source after import normalization. +Remote checks on the newly published downstream PR are pending/running, and the +source PR may rerun checks when this documentation-only delivery record is pushed. +No PR is merged and no production host is changed. + +## Lessons Extracted + +Source PR checks must precede downstream vendor writes. + +## Followup + +No implementation item is deferred; independent platform gaps remain in SOW-0037. + +## Regression Log + +This is publication and integration of SOW-0036, not a new regression. + +## Vendor Baseline And Migration Plan + +- Source candidate: netdata/plugin-ipc @ 5c1e14545fd3c367db3851084bce949088ee1c0f, PR #17. +- Historical baseline: plugin-ipc 37cce82d4b0e1e9d1fbee2ff2ab561cc9920ffa6; + Netdata d5796cb0a841adaabdd68d427664155d8e30d46e. Reconstructed 227 source + files across C include/src, Rust src and Go package: zero normalized mismatches. +- Netdata target: 4464a9ed71c8a7351b540ce4ca207e1418faffea. All 227 normalized + files exactly match plugin-ipc 0d17a9d (candidate parent). +- Source gap since historical baseline: five Rust compatibility files already + backported in SOW-0033, followed by four time64 source/test edits from SOW-0036. +- Downstream gap: those same five Rust compatibility edits, all exactly retained; + Cargo.toml edition/dependencies use workspace settings and must remain unchanged. +- Migration: source wins for C netipc_shm.c, Rust shm.rs/shm_tests.rs, Go shm_linux.go. + Preserve all C wrappers, Cargo metadata/lockfiles and Netdata Go module paths. +- The new standalone Rust tests/shm_timeout.rs is self-contained and useful in the + downstream workspace. The existing vendor script copies src/ only, so copy this + exact upstream fixture separately after preflight. This yields five changed + Netdata files and zero normalized diff, including the new fixture. +- No downstream source fix remains to backport; no conflict or design choice exists. +- Preflight so far: code scanning, Dependabot and secret scanning queries return + zero open alerts. The initial source checks were still running at that checkpoint; + the subsequent passed-preflight section records approval to copy. + +## Source Preflight Passed - 2026-09-27 + +- Candidate: netdata/plugin-ipc @ 5c1e14545fd3c367db3851084bce949088ee1c0f. +- All eight Actions workflows completed successfully: Runtime Safety 36325853646, + Static Analysis 36325853609, CodeQL 36325853650, Codacy Local Analysis 36325853629, + Codacy Coverage 36325853630, Supply Chain Security 36325853656, Dependency Review + 36325853615 and Code Quality 36325851891. All applicable check-runs succeeded. +- ARM ABI, native sanitizers, Windows MSYS runtime, MSRV/latest Rust, all Go static + targets, Go race, CodeQL and coverage gates passed. Valgrind, OSV and Scorecard + are skipped by their configured event predicates, not failed or missing PR jobs. +- Current code scanning, Dependabot and secret-scanning open alert counts: 0/0/0. + PR merge analysis 867d1d21f34956ee07427c3ea3d537912e54639c has zero findings from + CodeQL, Semgrep, gosec and Codacy analyses. +- CodeRabbit's optional review status remains pending. It is not an Actions + validation or security-scanner result; main has no required status-check + contexts and requires code-owner review for merge. No merge is requested here. +- Decision: proceed with vendoring. Source SDK tree is clean; the only uncommitted + source artifact is the active delivery SOW, which is not a vendored input. From 6aa82011e6e2e1d134fa0f240fbebf20db201396 Mon Sep 17 00:00:00 2001 From: Costa Tsaousis Date: Sun, 27 Sep 2026 18:41:10 +0300 Subject: [PATCH 3/3] tests: assert the compiled Rust SHM timeout ABI in each mode --- ...39-20260927-rust-timeout-abi-assertions.md | 129 ++++++++++++++++++ docs/level1-posix-shm.md | 7 +- docs/netipc-integrator-skill.md | 2 + tests/run-rust-shm-timeout-abi.sh | 11 +- 4 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 .agents/sow/done/SOW-0039-20260927-rust-timeout-abi-assertions.md diff --git a/.agents/sow/done/SOW-0039-20260927-rust-timeout-abi-assertions.md b/.agents/sow/done/SOW-0039-20260927-rust-timeout-abi-assertions.md new file mode 100644 index 0000000..acfd144 --- /dev/null +++ b/.agents/sow/done/SOW-0039-20260927-rust-timeout-abi-assertions.md @@ -0,0 +1,129 @@ +# SOW-0039 - Assert Rust timeout ABI test layouts + +## Status + +Status: completed + +Sub-state: layout enforcement validated; three AI review threads explained and resolved; publishing with implementation. + +## Requirements + +### Purpose +Address the three reviewed AI findings and resolve each with a fix or evidence. + +### User Request +The user explicitly requests fixing code/docs or explaining incorrect findings and resolving all comments. + +### Assistant Understanding +Facts: both ARM Rust ABI modes currently pass and print different sizes, but the runner does not assert them. The GNU riscv32 claim assumes a time64 alias absent from pinned libc. The Netdata private-field finding applies a Go collector skill to a Rust unit test; the public integration test retains normal spinning. +Inferences: enforcing the existing diagnostic in the mode-owning runner closes the validation gap without changing shared fixtures or transports. +Unknowns: no unresolved implementation decisions. + +### Acceptance Criteria +- The runner checks pointer, tv_sec and timespec sizes for both intended modes. +- Actual ARM execution passes on Rust MSRV and stable; wrong/missing layouts and test failures are rejected. +- Every existing AI review thread receives an evidence-based reply and is resolved. + +## Analysis + +Sources checked: current/paused SOW-0015/0021/0027 and pending SOW-0031/0032/0035/0037; completed SOW-0036 and SOW-0038; empty local specs directory; project vendoring skill; docs/code-organization.md; docs/level1-posix-shm.md; Rust public fixture and ABI runner; C ABI runner and Go timeout construction. +Current state: no active overlapping execution. This is extra validation, not a failed previously proven timeout repair, so no regression reopening is required. +Risks: parsing a diagnostic introduces a small coupling; exact whole-line matching and negative cases make it explicit. + +## Pre-Implementation Gate + +Status: ready + +Problem / root-cause model: mode selection is not independently checked against compiled field sizes. Both runs could silently test one ABI if a dependency changes its configuration behavior. +Evidence reviewed: public fixture prints pointer width, actual tv_sec size (labelled time_t), and timespec size; libc build.rs tracks the mode environment; real ARM runs show 4/4/8 and 4/8/16. +Affected contracts and surfaces: standalone test runner and validation guidance only; no C/Rust/Go API, protocol or production transport changes. +Existing patterns to reuse: low-priority helper, mktemp/EXIT cleanup, pipefail and the existing public-fixture diagnostic. +Risk and blast radius: runner failure behavior only; preserve cargo exit status with pipefail and stream output with tee. +Sensitive data handling plan: public code paths, commit IDs and sanitized test summaries only in SOWs, specs, docs, project skills, instructions and code comments; no credentials, identities, endpoints or raw private logs. +Implementation plan: capture each invocation into one temporary log; require the exact expected ABI line per mode; explain asserted layouts in public docs and integrator guidance. +Validation plan: execute ARM time32/time64 with MSRV 1.91.0 and stable; inject wrong, missing and failing command outputs using a temporary command harness; ShellCheck, bash syntax, diff check and SOW audit. +Artifact impact plan: +- AGENTS.md: no workflow change. +- Runtime project skills: no vendoring occurs; source-owned runner only. +- Specs: validation section of authoritative SHM doc changes, no duplicate local spec. +- End-user/operator docs: explain layout enforcement. +- End-user/operator skills: mention asserted compiled layout in integrator validation guidance. +- SOW lifecycle: new validation-hardening SOW, completed with its implementation in one commit; existing work remains paused/pending. +Open-source reference evidence: rust-lang/libc @ 42620ffc4109dc32e02f1cae9e63a3f4311b4b71, src/unix/linux_like/linux/gnu/b32/riscv32/mod.rs:667 and src/unix/linux_like/linux/musl/b32/riscv32/mod.rs:644 confirm distinct syscall constants. +Open decisions: none; user authorized fixes, explanations and thread resolution. + +## Implications And Decisions + +Keep the shared fixture unchanged and assert its actual compiled diagnostic in the runner that owns mode selection. No downstream copy is required. The two false-positive findings receive source-linked explanations. + +## Plan + +1. Explain and resolve the two inaccurate findings. +2. Implement, validate, document and commit the layout gate. +3. Push, reply with the fix and resolve the remaining thread; refresh all selected comments. + +## Execution Log + +### 2026-09-27 + +- Verified both false positives and replied/resolved them individually using the Netdata project PR review workflow. +- Implementation gate recorded before edits. Added runner layout enforcement and synchronized validation documentation. +- Source pre-push CI: 33 success, 3 configured skips, 1 neutral, no failures/running checks. Netdata retains one pre-compilation ARM container exec-format infrastructure failure and three running checks; no new downstream changes. + +## Validation + +Acceptance criteria evidence: +- Runner asserts the expected pointer/seconds/timespec representation independently of libc configuration. +- All three original threads received substantive replies and resolveReviewThread returned true individually: plugin-ipc PR17 discussions 4115703293 and 4115703296; Netdata PR24049 discussion 4115730229. + +Tests or equivalent validation: +- Actual ARM/QEMU runner passes time32 (4/4/8 bytes) and time64 (4/8/16) under Rust 1.91.0 and stable 1.98.1. Installed the missing MSRV ARM standard library before rerunning. +- Temporary negative command harness: old runner accepts duplicated legacy ABI; new runner rejects it and missing ABI output with exit 1; preserves Cargo exit 7; correct modes pass and temporary logs are removed in every case. +- ShellCheck, bash syntax and git diff --check pass. SOW audit run before commit. + +Real-use evidence: +- Production Rust SHM receives in the public fixture wait at least 100 ms and 1100 ms on both actual emulated ABIs and wake on delayed messages for finite/infinite/max budgets. + +Reviewer findings: +- Qodo ABI assertion request implemented in the mode-owning runner, avoiding changes to the shared fixture. +- Qodo GNU riscv32 claim rejected against pinned upstream constants: claimed time64 alias exists only for musl, which the branch handles. No full GNU riscv32 runtime support is asserted. +- Qodo spin-test claim rejected: normal public construction is already covered, while the unit test deliberately isolates blocking; cited Go collector skill is outside Rust transport scope. +- Direct self-review of the runner/docs working diff is sufficient: no production behavior changes or material unresolved interactions; negative tests cover failure propagation, stale output and cleanup. No repeated external review is needed for this scoped safeguard. + +Same-failure scan: +- Searched ABI diagnostics/mode switches in tests, Rust integration tests and Runtime Safety CI. This is the single Rust mode-owning runner. C time64 CI already uses NIPC_TEST_REQUIRE_TIME64_32 static assertions; no matching missing check in the selected scope. + +Sensitive data gate: +- Durable changes contain public paths, ABI sizes and sanitized validation evidence only; no credentials, identities, private endpoints or incident logs. + +Artifact maintenance gate: +- AGENTS.md: unchanged because responsibilities/workflow are unchanged. +- Runtime project skills: unchanged; no Netdata vendoring or new integration procedure. +- Specs: authoritative docs/level1-posix-shm.md validation section updated; empty local specs directory needs no duplicate of public guidance. +- End-user/operator docs: updated the expected ABI and failure behavior. +- End-user/operator skills: docs/netipc-integrator-skill.md now requires both layout and behavior checks. +- SOW lifecycle: completed file moved to done and committed with its runner/docs changes; paused/pending SOWs untouched. + +Specs update: validation contract clarified in public SHM documentation; runtime protocol unchanged. +Project skills update: vendoring gate unaffected because runner/docs changes do not alter Netdata's vendored sources. +End-user/operator docs update: expected representations and rejection behavior documented. +End-user/operator skills update: compiled-layout verification added to integration checks. +Lessons: do not infer ABI coverage solely from a configuration toggle; verify the actual compiled representation. +Follow-up mapping: layout check implemented; two inaccurate findings rejected with evidence; independent 32-bit build coverage remains tracked in SOW-0037. No new deferred implementation. + +## Outcome + +Rust cross-ABI validation now fails when the selected layout was not actually compiled. All three original AI findings have been addressed and resolved; remote CI on the new commit is not claimed here. + +## Lessons Extracted + +Cross-ABI test configuration must be checked against the representation actually compiled. + +## Followup + +No additional source transport work is included. Existing independent 32-bit build gaps remain in SOW-0037. + +## Regression Log + +No regression of a previously proven timeout result; this adds an explicit validation safeguard. + diff --git a/docs/level1-posix-shm.md b/docs/level1-posix-shm.md index 34eb063..066a06b 100644 --- a/docs/level1-posix-shm.md +++ b/docs/level1-posix-shm.md @@ -358,7 +358,10 @@ and maximum API timeouts. For Rust, install `qemu-arm` and the rustup target `arm-unknown-linux-musleabihf`, then run `bash tests/run-rust-shm-timeout-abi.sh`. This runs the public-API fixture with both libc crate musl time layouts, using its `RUST_LIBC_UNSTABLE_MUSL_V1_2_3` -test configuration for time64. Runtime Safety CI checks both C ARM glibc -layouts and both Rust ARM musl layouts. Emulation validates syscall ABI +test configuration for time64. The runner requires the fixture's compiled ABI +diagnostic to report 4-byte pointers, 4/8-byte seconds fields and 8/16-byte +timespecs for time32/time64 respectively; a missing or unexpected layout fails +the run even when the timeout assertions pass. Runtime Safety CI checks both +C ARM glibc layouts and both Rust ARM musl layouts. Emulation validates syscall ABI behavior; it does not establish production CPU usage or full C/Rust/Go support for that architecture. diff --git a/docs/netipc-integrator-skill.md b/docs/netipc-integrator-skill.md index 8b08494..9453860 100644 --- a/docs/netipc-integrator-skill.md +++ b/docs/netipc-integrator-skill.md @@ -33,6 +33,8 @@ When integrating on 32-bit Linux, run the target-libc timeout regression in [POSIX SHM validation](level1-posix-shm.md#timeout-abi-regression-validation). A timeout error alone does not prove that the thread blocked for its budget. Verify elapsed waiting and delayed-peer wakeup, especially with time64 libc. +The Rust ABI runner also asserts the compiled layout for each requested mode; +both timeout behavior and the expected ABI must pass. The C ABI fixture does not replace building and testing each language consumer for the target architecture or measuring the deployed service's CPU use. diff --git a/tests/run-rust-shm-timeout-abi.sh b/tests/run-rust-shm-timeout-abi.sh index 429e2cb..527ea50 100755 --- a/tests/run-rust-shm-timeout-abi.sh +++ b/tests/run-rust-shm-timeout-abi.sh @@ -10,8 +10,17 @@ rust_sysroot=$(rustc --print sysroot) export CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_LINKER="$rust_sysroot/lib/rustlib/$rust_host/bin/rust-lld" export CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_RUSTFLAGS='-C linker-flavor=ld.lld' export CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_RUNNER='timeout 25 qemu-arm' +abi_log=$(mktemp "${TMPDIR:-/tmp}/nipc-rust-timeout-abi.XXXXXX") +trap 'rm -f "$abi_log"' EXIT for time64 in 0 1; do RUST_LIBC_UNSTABLE_MUSL_V1_2_3=$time64 cargo test \ --manifest-path "$root/src/crates/netipc/Cargo.toml" \ - --target arm-unknown-linux-musleabihf --test shm_timeout -- --nocapture + --target arm-unknown-linux-musleabihf --test shm_timeout -- --nocapture | tee "$abi_log" + # The fixture reports the compiled tv_sec size as time_t. Check the actual + # layout so an ignored libc mode switch cannot silently duplicate coverage. + expected_abi="ABI: pointer=4 time_t=$((4 + 4 * time64)) timespec=$((8 + 8 * time64))" + if ! grep -Fxq "$expected_abi" "$abi_log"; then + echo "Unexpected Rust ABI for time64=$time64; expected: $expected_abi" >&2 + exit 1 + fi done