Skip to content

feat: make C API exports thread-safe - #30

Open
not-matthias wants to merge 2 commits into
mainfrom
cod-3324-codspeed-runner-hangs-when-using-divans-threads-feature
Open

feat: make C API exports thread-safe#30
not-matthias wants to merge 2 commits into
mainfrom
cod-3324-codspeed-runner-hangs-when-using-divans-threads-feature

Conversation

@not-matthias

@not-matthias not-matthias commented Sep 4, 2026

Copy link
Copy Markdown
Member

NOTE: Didn't put the spin-lock into the C exports as we still may want to have it be lock-free in simulation/other instruments. Handling the synchronization at the exact place where it's needed, rather than at the boundary is better imo.

@not-matthias
not-matthias force-pushed the cod-3324-codspeed-runner-hangs-when-using-divans-threads-feature branch 2 times, most recently from 7a612d2 to 6f49f39 Compare September 4, 2026 13:45
@not-matthias
not-matthias marked this pull request as ready for review September 4, 2026 13:46
@not-matthias
not-matthias force-pushed the cod-3324-codspeed-runner-hangs-when-using-divans-threads-feature branch from 6f49f39 to d7df0f8 Compare September 4, 2026 13:46
@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds process-wide serialization for runner FIFO request/response exchanges, per-handle locking for environment data, atomic feature flags, concurrency tests, regenerated C distribution artifacts, and an explicit thread-safety contract. Two synchronization gaps remain:

  • FIFO reader teardown can drain another handle's in-flight acknowledgement because it is outside the global lock.
  • Per-command serialization does not make concurrent benchmark lifecycle sequences semantically safe, despite the new documentation claiming that it does.

Confidence Score: 3/5

The PR is not safe to merge until shared FIFO teardown is synchronized and the concurrent benchmark-lifecycle guarantee is corrected or implemented.

Per-command locking prevents request/reply pairing between normal exchanges, but an unlocked reader teardown can still consume another handle's reply. Additionally, arbitrary concurrent start and stop operations are serialized in nondeterministic order against a runner with one global lifecycle state, causing dropped boundaries and incorrect benchmark attribution.

Files Needing Attention: src/runner_fifo.zig, CUSTOM_HARNESS.md

Important Files Changed

Filename Overview
src/runner_fifo.zig Adds process-global request/response serialization, but leaves shared acknowledgement-FIFO draining outside that synchronization.
src/lock.zig Introduces an acquire/release yield-spinlock and process-global lock instance with focused mutual-exclusion tests.
src/environment/root.zig Serializes environment setters and reporting on each handle without introducing a confirmed contract violation.
src/features.zig Replaces the non-atomic feature bit set with atomic bitwise updates and reads.
src/c.zig Adds a concurrent multi-handle FIFO test, though its scheduling does not deterministically cover teardown draining an in-flight reply.
CUSTOM_HARNESS.md Adds a thread-safety guarantee that overstates the safety of concurrently interleaved benchmark lifecycle calls.
dist/core.c Regenerates the distributed Linux and macOS C implementations with the new locks and atomic feature state.

Sequence Diagram

sequenceDiagram
    participant A as Handle A
    participant L as Global exchange lock
    participant C as Control FIFO
    participant R as Runner
    participant K as Shared acknowledgement FIFO
    participant B as Handle B teardown

    A->>L: acquire
    A->>C: send command
    C->>R: command
    R-->>K: reply
    B->>K: drain during deinit (unlocked)
    K--xB: consumes A's reply
    A->>K: wait for reply
    K--xA: timeout or partial frame
    A->>L: release
Loading

Fix all with Greploop Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
### Issue 1
src/runner_fifo.zig:56-57
**Teardown Can Steal Replies**

The global lock protects only `sendCmd` and its response wait, while `RunnerFifo.deinit` drains the process-wide acknowledgement FIFO without taking that lock. If one handle is destroyed—or an instrument probe fails and tears itself down—while another handle is waiting for a reply, the drain can consume that reply from the shared pipe. The in-flight request can then time out or decode a partial or stale frame. Reader teardown and draining need the same process-wide synchronization as normal exchanges.

### Issue 2
CUSTOM_HARNESS.md:224
**Concurrent Lifecycles Lose Boundaries**

This promises that per-thread benchmark iterations may call `start_benchmark` and `stop_benchmark` concurrently, but the lock only serializes individual commands and does not preserve each thread's lifecycle. The runner keeps one process-wide `benchmark_started` flag, so it drops a second start as a duplicate and drops a stop received before a start. Concurrent iterations can therefore leave measurement windows unbalanced or associate identities and markers with the wrong benchmark. Either restrict the guarantee to calls whose semantic order is externally coordinated or implement per-iteration lifecycle coordination. See the runner's [shared lifecycle state and command handling](https://github.com/codspeedhq/codspeed/blob/HEAD/src/executor/shared/fifo.rs#L182-L238).

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: 6f49f39 | Re-trigger Greptile

Comment thread src/runner_fifo.zig Outdated
Comment thread CUSTOM_HARNESS.md Outdated
Divan's threads feature calls the hooks concurrently from several threads,
which interleaved writes to the runner control FIFO and let a reader consume
another thread's ack, hanging the runner.

Serialize the exports behind a process-global lock. The lock is global rather
than per-instance because the FIFO paths are fixed, so all InstrumentHooks in
a process share one transport and one ack stream.

The lock is a CAS yield-spinlock in Zig instead of std.Thread.Mutex or
pthread_mutex_t: the sources are transpiled to C once per OS, so a futex
mutex would bake x86_64 syscall asm into the output and pthread_mutex_t would
bake in its transpile-time size, both wrong on other architectures.
The macos-latest image now ships an SDK whose libSystem Zig 0.14 cannot link
against, so every job that links natively fails with undefined libc symbols.
setup-zig resolves the toolchain from build.zig.zon's minimum_zig_version, and
0.14.1 fails the same way, so the runner image is pinned instead.

This reproduces on an unmodified main, so it is independent of the changes in
this branch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant