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
fspy-sparse-file-shmfrom
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
fspy-sparse-file-shmfrom
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 keeping full write access to the mapping, so the receiver can read frames while a straggler mutates them — the root cause pinned in the #544 investigation.

This replaces the lock with a gate stored in the shared memory itself, where a writer cannot drop it while still being able to write: a claim is admitted and counted by one atomic word, and the runner's close is one fetch_or at root-process exit that atomically fences all future claims and reports whether any write was still in flight. If none was — the overwhelmingly common case, since in-flight windows are microseconds and the runner closes milliseconds after the exit it observes via wait — every claim provably ran to completion and the memory is frozen. Otherwise the run is conservatively not cached. The closed bit and the count sharing one word is load-bearing: splitting them into two atomics would reintroduce the very race the file lock had.

Semantics change: fspy no longer waits for lingering descendants after the root process exits, and accesses made after that exit are not tracked. This makes vp run's post-task timing predictable — a task that leaks a daemon no longer blocks the read step — and treats post-exit accesses as what they are, racy with respect to the task's contract.

That also fixes the hang where a cached task's tool leaves detached descendants: the receiver's exclusive flock used to wait on shared flocks inherited by Playwright/Chromium's setsid tree. The runner never waits now, the browser tree's post-exit accesses are dropped by design, and the run caches normally.

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 +140.95%  [+116.74% .. +160.31%]  overhead  +272.37%
dynamic/access             change  +6.58%  [ +4.51% ..  +8.37%]  overhead   +64.37%
static/launch              change +192.35%  [+158.39% .. +220.37%]  overhead  +670.93%
static/access              change  +0.39%  [ -6.93% ..  +7.87%]  overhead  +829.68%

macos

dynamic/launch             change  -2.28%  [ -8.90% ..  +3.17%]  overhead  +237.02%
dynamic/access             change  +6.09%  [-12.99% .. +130.84%]  overhead   +24.37%

windows

dynamic/launch             change  -4.75%  [ -8.60% ..  +2.95%]  overhead   +26.09%
dynamic/access             change  -0.17%  [ -1.03% ..  +0.86%]  overhead    +9.21%

@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 on lines +164 to +167
// Close the ipc channel now that the child has exited. We are not
// interested in path accesses from descendants after the main child
// has exited, and we do not wait for them either: closing is a
// single atomic operation that also fences out later writers.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Stop the seccomp supervisor without waiting for descendants

On Linux, when the root leaves a descendant alive under the inherited seccomp filter, this close is never reached at root exit: the immediately preceding supervisor.stop().await calls Supervisor::stop, whose handling loop drains its JoinSet until every notify listener reaches EOF (crates/fspy_seccomp_unotify/src/supervisor/mod.rs:52-55,123-125). A leaked daemon therefore still blocks the task indefinitely even when its stdio is redirected, so the supervisor handlers must be cancelled or detached rather than awaited before closing the channel.

AGENTS.md reference: AGENTS.md:L147-L153

Useful? React with 👍 / 👎.

Comment thread CHANGELOG.md Outdated
# Changelog

- **Fixed** Automatic file-access tracking now backs its shared memory with a sparse temporary file on every platform, so tasks are still tracked inside coding-agent sandboxes that deny POSIX shared memory and Unix domain sockets ([#563](https://github.com/voidzero-dev/vite-task/issues/563), [#576](https://github.com/voidzero-dev/vite-task/pull/576)).
- **Changed** Automatic file-access tracking now stops the moment a task's root process exits. Accesses made by leftover descendants afterwards are no longer recorded and never delay `vp run`, and a task that still had a traced process mid-write at that instant is conservatively not cached ([#544](https://github.com/voidzero-dev/vite-task/issues/544), [#396](https://github.com/voidzero-dev/vite-task/issues/396)).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Stop pipe draining when the root process exits

When a background descendant inherits the task's stdout or stderr, the claim that it can never delay vp run is not true for cached tasks: ExecutionMode::spawn_config forces cached executions to SpawnStdio::Piped (crates/vite_task/src/session/execute/mod.rs:218), and run_child awaits pipe_stdio to EOF before awaiting ChildHandle.wait (:593-608). The descendant keeps the pipe write end open after the root exits, so the run still hangs even though fspy's background wait has closed the gate; pipe draining needs to be coordinated with root-process termination.

Useful? React with 👍 / 👎.

@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
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 fspy-sparse-file-shm branch from 73b40b8 to 941a231 Compare July 29, 2026 02:14
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