Skip to content

fix(fspy): replace the IPC file lock with an in-mapping close gate - #577

Open
wan9chi wants to merge 1 commit into
agent/reapply-runner-fifofrom
fspy-close-gate
Open

fix(fspy): replace the IPC file lock with an in-mapping close gate#577
wan9chi wants to merge 1 commit into
agent/reapply-runner-fifofrom
fspy-close-gate

Conversation

@wan9chi

@wan9chi wan9chi commented Jul 28, 2026

Copy link
Copy Markdown
Member

Motivation

The current quiescence protocol attaches "may write" to a shared mapping but "is still writing" to a file-lock descriptor. A descendant that closes descriptors it does not recognize releases the lock while it keeps write access to the mapping, so the receiver can read frames while a straggler mutates them. The #544 investigation pinned this as the root cause.

This PR replaces the lock with a gate stored in the shared memory itself, where a writer cannot lose it while it can still write. One atomic word admits and counts each claim. The runner closes with one fetch_or at root-process exit, which fences all future claims and reports whether a write was still in flight. A zero count proves every claim ran to completion: the memory is frozen, and the runner reads and caches. A nonzero count means the run is not cached. In-flight windows last microseconds and the runner closes milliseconds after wait returns, so a nonzero count is rare. The closed bit and the count share one word on purpose; with two atomics, a writer could check the flag before the close and publish its count after it, which is the file lock's race in new clothes.

The close drops the shared memory's keeper, so the attach window ends with the close: a process that starts later fails at open, and a sender that attached earlier has its claims refused. On Linux, the seccomp supervisor's stop also returns without waiting: each listener task hands over what it recorded and a detached task keeps answering notifications, so a live filtered process keeps working while its later accesses go unrecorded. Draining those listeners to EOF used to mean waiting for every filtered descendant to exit, which reintroduced the daemon hang this PR removes.

A process may also call exit while another of its threads is mid-send, which would abandon a gate guard and mark the whole run incomplete. The preload now interposes exit and _exit and waits the few microseconds until in-flight sends finish before the process dies. Signals and crashes still skip this, and the runner handles those by not caching the run. Windows parity is a follow-up.

Semantics change: fspy no longer waits for lingering descendants after the root process exits, and it does not track accesses made after that exit. Collecting the trace no longer delays vp run; a task that leaks a daemon no longer blocks the read step. This fixes #396, where the receiver's exclusive flock waited on shared flocks inherited by Playwright/Chromium's setsid tree. Stdio pipe draining can still hold a run open and is tracked separately in #485.

Closes #544
Closes #396

🤖 Generated with Claude Code

wan9chi commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

fspy benchmark

linux

dynamic/launch             change +153.33%  [+130.44% .. +171.77%]  overhead  +280.11%
dynamic/access             change  +8.42%  [ +7.16% ..  +9.30%]  overhead   +67.00%
static/launch              change +193.86%  [+164.14% .. +221.79%]  overhead  +661.39%
static/access              change  -0.14%  [ -1.72% ..  +1.21%]  overhead  +818.13%

macos

dynamic/launch             change  -0.69%  [ -4.49% ..  +4.55%]  overhead  +224.77%
dynamic/access             change  +7.35%  [ -4.18% .. +13.03%]  overhead   +23.03%

windows

dynamic/launch             change  -0.89%  [ -4.59% ..  +4.34%]  overhead   +28.05%
dynamic/access             change  +1.58%  [ +0.18% ..  +2.64%]  overhead   +10.13%

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb42117817

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/fspy/src/unix/mod.rs
Comment thread CHANGELOG.md Outdated
@wan9chi
wan9chi force-pushed the fspy-sparse-file-shm branch from cfb9f15 to e67d3fa Compare July 28, 2026 11:18
@wan9chi
wan9chi force-pushed the fspy-sparse-file-shm branch from e67d3fa to b0ab415 Compare July 28, 2026 11:20
@wan9chi
wan9chi force-pushed the fspy-sparse-file-shm branch from b0ab415 to 73b40b8 Compare July 29, 2026 01:22
@wan9chi
wan9chi force-pushed the fspy-sparse-file-shm branch from 73b40b8 to 941a231 Compare July 29, 2026 02:14
@wan9chi
wan9chi force-pushed the fspy-sparse-file-shm branch from 941a231 to 5e527ad Compare July 30, 2026 01:42
@wan9chi
wan9chi force-pushed the fspy-sparse-file-shm branch from 5e527ad to f572f0d Compare July 30, 2026 02:14
@wan9chi
wan9chi force-pushed the fspy-sparse-file-shm branch from f572f0d to ca1932a Compare July 30, 2026 03:24
@wan9chi
wan9chi force-pushed the fspy-sparse-file-shm branch from ca1932a to dcb74d7 Compare July 30, 2026 03:43
@wan9chi
wan9chi force-pushed the fspy-sparse-file-shm branch from dcb74d7 to 54dbb9b Compare July 30, 2026 03:48
@wan9chi
wan9chi force-pushed the fspy-sparse-file-shm branch from 54dbb9b to 126f02b Compare July 30, 2026 03:51
@wan9chi
wan9chi force-pushed the fspy-sparse-file-shm branch from 126f02b to c86913c Compare July 30, 2026 04:11
@wan9chi
wan9chi force-pushed the fspy-close-gate branch 2 times, most recently from 38e7f20 to 5d642d1 Compare July 30, 2026 04:36
@wan9chi
wan9chi changed the base branch from fspy-sparse-file-shm to graphite-base/577 July 30, 2026 06:12
@wan9chi
wan9chi changed the base branch from graphite-base/577 to agent/reapply-runner-fifo July 30, 2026 06:12
@wan9chi
wan9chi force-pushed the agent/reapply-runner-fifo branch from 6499a5c to 32eaa5b Compare July 30, 2026 07:12
@wan9chi
wan9chi force-pushed the agent/reapply-runner-fifo branch from 32eaa5b to ee3a214 Compare July 30, 2026 07:35
@wan9chi
wan9chi force-pushed the agent/reapply-runner-fifo branch 2 times, most recently from 42ac131 to fb19d42 Compare July 30, 2026 07:50
@wan9chi
wan9chi force-pushed the fspy-close-gate branch 2 times, most recently from 652efbd to b933a1f Compare July 30, 2026 08:10
@wan9chi
wan9chi force-pushed the agent/reapply-runner-fifo branch from fb19d42 to b6aa5bb Compare July 30, 2026 08:10
@wan9chi
wan9chi force-pushed the agent/reapply-runner-fifo branch from b6aa5bb to 5a99844 Compare July 30, 2026 08:18
@wan9chi
wan9chi force-pushed the agent/reapply-runner-fifo branch 2 times, most recently from 3ca6460 to e8ea982 Compare July 30, 2026 09:03
The old quiescence protocol attached "may write" to the shared mapping but
"is still writing" to a file-lock descriptor. A descendant that closes
descriptors it does not recognize released the lock while keeping full
write access to the mapping, so the receiver could read frames while a
straggler was mutating them.

Put the gate in the shared memory itself, where a writer cannot drop it
while still being able to write: one atomic word admits and counts claims,
and the runner's close is a single `fetch_or` at root-process exit that
fences all future claims and reports whether any write was in flight. Zero
in flight proves every admitted claim ran to completion and the memory is
frozen; anything else means the run is conservatively not cached.

Tracking now stops when the root process exits instead of waiting for
lingering descendants, so a task that leaks a daemon no longer blocks the
read step, and post-exit accesses are treated as what they are: racy with
respect to the task's contract.

Closes #544.
Closes #396.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@wan9chi
wan9chi force-pushed the agent/reapply-runner-fifo branch from e8ea982 to 82f986f Compare July 30, 2026 09:06
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