Skip to content

Commit 5c1e145

Browse files
committed
Fix Linux SHM futex timeout ABI on time64 libc
1 parent 0d17a9d commit 5c1e145

15 files changed

Lines changed: 733 additions & 30 deletions

File tree

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
# SOW-0036 - Linux SHM futex timeout ABI portability
2+
3+
## Status
4+
5+
Status: completed
6+
7+
Sub-state: source repair, local ABI validation, documentation and CI configuration completed; no downstream deployment.
8+
9+
## Requirements
10+
11+
### Purpose
12+
13+
Preserve real blocking timeouts for idle SHM sessions on 32-bit time64 libc builds.
14+
15+
### User Request
16+
17+
The user supplied a corrected investigation identifying a libc/kernel timespec ABI
18+
mismatch in the C futex wrapper, with immediate timeouts and idle session CPU use.
19+
Treat this as an upstream repair; deployment and host configuration are outside scope.
20+
21+
### Assistant Understanding
22+
23+
Facts: C and Rust pass libc timespec pointers to SYS_futex. The receive timeout is
24+
an unsigned 32-bit millisecond duration. Go uses syscall.Timespec but hardcodes
25+
64-bit field assignments. Existing Rust timeout tests assert the error only.
26+
27+
Inferences: explicit kernel ABI marshalling repairs the C and Rust failure without
28+
changing the shared-memory layout or service polling policy.
29+
30+
Unknowns: affected deployed binaries have not been independently traced in this
31+
session; no production CPU reduction will be claimed from local tests.
32+
33+
### Acceptance Criteria
34+
35+
- Empty SHM receives actually wait for subsecond and multisecond budgets.
36+
- Finite and infinite receives still wake when a peer sends.
37+
- ARM time64 C regression fails before the fix and passes after it.
38+
- Native C/Rust/Go SHM tests and cross-language interop remain passing.
39+
- ABI policy and reproducible portability checks are documented.
40+
41+
## Analysis
42+
43+
Sources checked: docs/level1-posix-shm.md, docs/code-organization.md, C/Rust/Go SHM
44+
sources, C/Rust tests, CMakeLists.txt, runtime-safety workflow, SOW-0006 and SOW-0016.
45+
Pending/current SOWs concern scale, maintainability, downstream integration, platform
46+
build gating, permissions, and Rust style; all current SOWs are paused. This is a
47+
latent initial-implementation defect (C wrapper originates in f71db33), not a reversal
48+
of a completed SOW's specific time64 fix. No SOW claims prior 32-bit time64 validation.
49+
The local SOW specs directory contains only .gitkeep. The only runtime project skill
50+
is downstream vendoring preflight; it does not apply to source-only repairs.
51+
52+
Go 386 build inspection also found pre-existing UDS and test compilation errors;
53+
track those separately rather than expanding this repair into a platform port.
54+
55+
## Pre-Implementation Gate
56+
57+
Status: ready
58+
59+
Problem / root-cause model:
60+
61+
- On 32-bit time64 libc, libc timespec seconds occupy eight bytes, while the
62+
legacy futex ABI consumes kernel-sized seconds and nanoseconds. A subsecond
63+
timeout can therefore become zero. Rust shares this pointer-layout assumption.
64+
65+
Evidence reviewed:
66+
67+
- Source wrappers and callers, existing timeout tests, public SHM synchronization
68+
contract, local Linux UAPI types, and musl's __timedwait.c web source.
69+
- User live observations are reported evidence, not independently reproduced here.
70+
71+
Affected contracts and surfaces:
72+
73+
- Linux C and Rust SHM waiting, Go relative-time conversion, regression fixtures,
74+
portability test script, runtime CI, public SHM docs and integrator guide.
75+
- Public APIs, wire layout, service polling intervals and Windows remain unchanged.
76+
77+
Existing patterns to reuse:
78+
79+
- Preserve monotonic deadlines, EINTR/EAGAIN retries, shared FUTEX_WAIT/WAKE,
80+
low-priority test runner and existing SHM interop fixtures.
81+
82+
Risk and blast radius:
83+
84+
- Wrong ABI selection can busy-loop or hang all Linux SHM users. Keep legacy
85+
syscall support for older kernels; validate timeout and wake paths. The maximum
86+
relative timeout is UINT32_MAX milliseconds, whose seconds fit signed 32 bits,
87+
so legacy futex marshalling suffices wherever that syscall exists.
88+
89+
Sensitive data handling plan:
90+
91+
- Do not persist hostnames, host paths, private endpoints, personal data or raw
92+
investigation notes in SOWs, specs, docs, skills, agent instructions or comments.
93+
Record only source references, synthetic test paths and sanitized measurements.
94+
95+
Implementation plan:
96+
97+
1. Add public receive timing/wake regression tests and reproduce on ARM time64.
98+
2. Marshal kernel timeout fields in C and Rust; correct Go duration construction.
99+
3. Add repeatable 32-bit regression command and CI coverage; update public guidance.
100+
4. Run focused native, cross-ABI and interoperability validation, review changes,
101+
and commit implementation and completed SOW together.
102+
103+
Validation plan:
104+
105+
- Real ARM Linux user ABI execution under QEMU with musl time64, plus native tests.
106+
- Short and >1-second idle waits, delayed message wake, zero timeout wake,
107+
CPU-versus-wall waiting evidence, maximum API timeout interrupted by a message.
108+
- Rust stable and MSRV 1.91.0; C/Rust/Go SHM and service SHM interop.
109+
- Search raw timed syscalls, review ABI/endian/null handling, diff check, SOW audit.
110+
111+
Artifact impact plan:
112+
113+
- AGENTS.md: no new responsibility or global workflow.
114+
- Runtime project skills: source-only repair does not change downstream preflight.
115+
- Specs: clarify ABI invariant in authoritative docs; avoid duplicate SOW spec.
116+
- End-user/operator docs: describe portability check and its validation limits.
117+
- End-user/operator skills: add 32-bit validation guidance to integrator guide.
118+
- SOW lifecycle: new SOW for latent initial defect; track separate Go port issues.
119+
120+
Open-source reference evidence:
121+
122+
- Web reference: https://git.musl-libc.org/cgit/musl/tree/src/thread/__timedwait.c
123+
(blob 666093be98516a1c84b2997f075a8dbfcb797b2c), explicitly marshals futex timeouts.
124+
No external mirrored/cloned repository was used.
125+
126+
Open decisions:
127+
128+
- No product decision is needed: restore existing blocking semantics and retain
129+
older-kernel compatibility. Do not disable cgroups or deploy changes.
130+
131+
## Implications And Decisions
132+
133+
Use the legacy futex syscall when available with its kernel layout, since every
134+
supported relative timeout fits; time64-only targets require a two-int64 layout.
135+
This avoids depending on new kernel support merely because libc uses time64.
136+
137+
## Plan
138+
139+
Follow the four gate implementation steps above, keeping other SOWs paused.
140+
141+
## Execution Log
142+
143+
### 2026-09-27
144+
145+
- Confirmed the unsafe libc pointer handoff in C and Rust.
146+
- Confirmed Go 386 build failures in SHM duration construction, UDS Iovlen/sendmsg,
147+
and oversized test literals. Broader UDS portability is tracked in SOW-0037.
148+
- Reproduced C immediate expiry on ARM musl and i386 glibc time64. Native x86_64
149+
and i386 time32 pass the same fixture before the repair.
150+
- Added explicit kernel-word marshalling in C and Rust, preserving null infinite
151+
waits and legacy syscall use for representable relative durations.
152+
- Rust time64 builds exposed private timespec padding: use Default initialization
153+
in transport and the benchmark clock helper. The benchmark helper is built by
154+
Cargo integration tests; no benchmark methodology or performance claim changed.
155+
- Reproduced the old Rust wrapper under ARM musl time64 in a temporary standalone
156+
crate with only the timespec initialization compatibility adjustments retained:
157+
100 ms returned in 0.894 ms. Repaired public-API test passes time32 and time64.
158+
- Added C native CTest fixture, C cross-ABI script, Rust public-API integration
159+
fixture and cross-ABI script, and Runtime Safety ARM CI job.
160+
- User asked about libc wrappers and language scope. Confirmed that syscall does
161+
not marshal timespec, musl helpers are internal, C/Rust share the issue, and
162+
pure Go's syscall.Timespec does not have this libc/kernel mismatch.
163+
- Full Rust ARM unit tests exposed a separate pthread_t Send test compilation
164+
failure; tracked in SOW-0037. The standalone SHM public-API fixture runs without
165+
that unrelated unit-test dependency.
166+
167+
## Validation
168+
169+
Acceptance criteria evidence:
170+
171+
- C ARM musl time64 before: 100 ms returned in 0.278 ms; 1100 ms returned in
172+
1000.168 ms; delayed finite receive returned timeout. After: 100.308 ms and
173+
1100.151 ms, all finite/infinite/UINT32_MAX message wake checks passed.
174+
- C i386 glibc time64 independently failed before and passed after. i386 time32
175+
and native x86_64 pass. Big-endian ARM musl time64 (nsec offset 12) also passes.
176+
- Rust ARM musl time32 and time64 public API waits and delayed-message checks pass;
177+
old wrapper with time64 returns in under 1 ms and fails the elapsed-time assertion.
178+
- C idle test records less than 1 ms process CPU per 100 ms wait under ARM QEMU;
179+
this is local blocking evidence, not a production performance estimate.
180+
181+
Tests or equivalent validation:
182+
183+
- Low-priority CMake configure/build and focused CTest: 7/7 passed (C SHM,
184+
timeout ABI, C service, Rust SHM, Go SHM, SHM interop, service SHM interop).
185+
- After Rust constructor updates: rebuilt and reran affected Rust SHM, C timeout
186+
and both interop suites: 4/4 passed. Interop covers all nine C/Rust/Go pairings.
187+
- Rust 1.91.0 and latest stable 1.98.1: 50 SHM unit tests and public-API
188+
integration fixture passed on each. Latest stable also passed both ARM musl
189+
time layouts and the Clippy correctness/suspicious gate.
190+
- C cross-ABI commands: run-shm-timeout-abi.sh with native cc, cc -m32, cc -m32
191+
-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64, Zig ARM musl and Zig big-endian ARM musl.
192+
- run-rust-shm-timeout-abi.sh: both ARM libc crate time configurations pass.
193+
- Actionlint, ShellCheck, Rust format check, YAML parse and diff check pass.
194+
- Clippy correctness/suspicious gate passes; existing advisory warnings remain.
195+
Added a narrow documented allowance for field reassignment because the suggested
196+
struct literal fails to compile with libc's private time64 padding.
197+
- New GitHub job is configured but has not run remotely; local C glibc coverage
198+
uses i386 and local ARM C coverage uses musl. No CI result is claimed.
199+
200+
Real-use evidence:
201+
202+
- Public SHM server/client mappings with no traffic actually block; forked C
203+
peers and Rust thread peers wake finite/infinite/maximum-timeout receives.
204+
- Production hosts and downstream source were not modified or remeasured.
205+
206+
Reviewer findings:
207+
208+
- Assistant self-review checked timeout width bounds, null pointers, shared futex
209+
flags, x32 kernel word size, endian handling, Rust private padding, cleanup and
210+
CI prerequisites. Corrected missing explicit cross-libc headers in the CI install
211+
and ShellCheck's masked-command-status warning. No independent reviewer used.
212+
213+
Same-failure scan:
214+
215+
- Searched C/Rust/Go raw syscall and timespec use. Only C and Rust SHM wait wrappers
216+
passed libc timespec to raw timed syscalls; both repaired. Rust benchmark clock
217+
helper also needed Default construction for time64 compilation. Go uses kernel
218+
Timespec; fixed its architecture-dependent duration construction. Remaining
219+
independent 32-bit builds are represented by pending SOW-0037.
220+
221+
Sensitive data gate:
222+
223+
- Durable changes contain synthetic test paths and sanitized timing evidence,
224+
not host identities, private endpoints, raw investigation notes, personal data
225+
or credentials. Public musl source URL and source file paths are safe references.
226+
227+
Artifact maintenance gate:
228+
229+
- AGENTS.md: unchanged; responsibilities, protocol layers and low-priority workflow
230+
are preserved. No new project-wide guardrail is required.
231+
- Runtime project skills: unchanged; no downstream copy or preflight workflow changed.
232+
- Specs: updated docs/level1-posix-shm.md with local ABI invariant; no duplicate
233+
.agents/sow/specs document needed for an existing public contract.
234+
- End-user/operator docs: docs/level1-posix-shm.md includes reproducible C/Rust
235+
portability commands and explicitly limits emulator evidence.
236+
- End-user/operator skills: docs/netipc-integrator-skill.md adds target-libc timeout
237+
validation guidance; it does not claim complete 32-bit language support.
238+
- SOW lifecycle: SOW-0036 closes with the implementation in the same commit;
239+
SOW-0037 remains open/pending for independent platform build gaps. Existing
240+
current SOWs remain paused. No archived TODO history changed.
241+
242+
Specs update: authoritative SHM spec updated as above; wire layout unchanged.
243+
244+
Project skills update: runtime vendoring skill unchanged because no vendoring occurs.
245+
246+
End-user/operator docs update: public ABI requirement and test commands updated.
247+
248+
End-user/operator skills update: integrator guide points consumers to target testing.
249+
250+
Lessons:
251+
252+
- A timeout error alone cannot prove real waiting. Check elapsed time and wakeup.
253+
- libc time types are not raw syscall layouts; test both widths and endian variants.
254+
255+
Follow-up mapping:
256+
257+
- Timeout marshalling, tests, docs and CI: implemented in SOW-0036.
258+
- Go UDS 32-bit and Rust pthread_t unit-test build issues: tracked in SOW-0037.
259+
- Deployment, host configuration changes and CPU claims: outside the source-repair
260+
scope; no promised production outcome is marked complete here.
261+
262+
## Outcome
263+
264+
C and Rust marshal the selected futex timeout ABI explicitly. Go constructs its
265+
kernel Timespec portably. Subsecond/multisecond waiting and delayed-peer wakeup
266+
are covered by real cross-ABI execution, native tests and CI configuration.
267+
Public APIs, wire layout and service polling intervals are unchanged. Production
268+
CPU reduction remains unmeasured because deployment was outside this task.
269+
270+
## Lessons Extracted
271+
272+
Timeout error assertions alone cannot distinguish real waiting from immediate expiry.
273+
274+
## Followup
275+
276+
Independent Go 32-bit UDS and Rust pthread_t unit-test build failures are tracked
277+
in pending SOW-0037. No unrepresented deferred implementation remains.
278+
279+
## Regression Log
280+
281+
No prior completed time64 repair exists; this is a latent initial defect.

0 commit comments

Comments
 (0)