Skip to content

fix(execute): stop waiting for processes a task left running - #875

Open
Dunqing wants to merge 3 commits into
voidzero-dev:mainfrom
Dunqing:fix-leftover-process-wait
Open

Dunqing wants to merge 3 commits into
voidzero-dev:mainfrom
Dunqing:fix-leftover-process-wait

Conversation

@Dunqing

@Dunqing Dunqing commented Oct 22, 2026

Copy link
Copy Markdown
Member

Motivation

After a task's process exits, vp run keeps reading the task's stdout and stderr until end of file, and keeps its IPC server running until every client disconnects. Neither wait has a limit, and neither depends on the task: a process the task started can inherit the pipes or connect to the runner and hold them open. vp run then waits until that process exits, which for a server is never. This affects cached tasks, whose output is always piped, and --log=grouped and --log=labeled.

A common way to hit it is a test setup that starts vp preview and stops it with child.kill(). The kill reaches only the vp wrapper (voidzero-dev/vite-plus#3090), and the Vite server keeps its connection to the runner. With that setup as a cached task, vt run on main was still waiting when timeout killed it at 25 s. With this change it exits after about 4 s, 2 s of which is the grace period below.

Changes

  • run_child waits for the child and drains its pipes at the same time. Once the child exits, the pipes get LEFTOVER_GRACE (2 s) to reach end of file, and then the IPC server gets 2 s to drain. Anything still open after that belongs to a process the task left running: the runner stops reading and stops polling the IPC driver, whose connections close with the run's state.
  • Such a run isn't cached (CacheNotUpdatedReason::ProcessLeftRunning), since that process's output or its reports to the runner may be missing. vp run --last-details shows → Not cached: the task exited, but a process it started kept its output or runner connection open. fspy already stops recording accesses once the task's own process exits.
  • vt exits without dropping its tokio runtime. The IPC transport reads its FIFOs on the blocking pool, and dropping the runtime waits for a read that a leftover client keeps blocked. vite-plus already sends the result to JS before dropping its runtime and then calls process.exit.
  • New vtt linger <ms> <stdio|ipc> leaves a child holding stdout or an IPC connection, for the new leftover_process e2e fixture. Both cases time out on main. They don't run on Windows, where the task's Job Object ends such processes once the task exits.

A process the task left running that keeps writing to the piped output now gets a broken pipe after the grace period, where vp run used to wait for it. Output inherited directly from the terminal (the default for uncached tasks) is unchanged.

Related: #485 reports the same pipe wait on Windows, and #486 proposes polling the wait and the pipes together for it, which this change also does. It hasn't been tested on Windows. #724 also changes run_child.

After a task's process exited, `vp run` kept reading its stdout and
stderr until end of file, and kept serving its IPC connections until
every client disconnected. A process the task started that inherited the
pipes, or connected to the runner, held them open, so the run waited
until that process exited, which for a server is never. This affected
cached tasks, whose output is always piped, and `--log=grouped` and
`--log=labeled`.

Once the task's process exits, its pipes now get two seconds
(`LEFTOVER_GRACE`) to close, and then the IPC driver gets two seconds to
drain. Anything still open belongs to a process the task left running:
the runner stops reading, leaves the connection to be closed with the
run's state, and doesn't cache the run (`ProcessLeftRunning`), since
output or reports may be missing. `--last-details` says so. fspy already
stops recording when the task's process exits.

The IPC transport reads its FIFOs on tokio's blocking pool, and such a
read can't be cancelled, so `vt` now exits without dropping its runtime,
which would wait for it. vite-plus sends the result to JS before
dropping its runtime and then calls `process.exit`.

`vtt linger` leaves such a process behind for the new e2e fixture.
@Dunqing
Dunqing requested a review from wan9chi October 22, 2026 07:05
@Dunqing
Dunqing marked this pull request as ready for review October 22, 2026 07:08

This branch has not been deployed

No deployments
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.

2 participants