You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
adversarial: keep executor close bounded when a child holds stdout - #586
Fixes#503 (executor close blocks on inherited stdout).
This replaces #532, which was closed. This branch is a single commit on current dev, with no merge commits.
Summary
ExecutorClient.close() closed the buffered stdout stream after the wrapper exited. If a child it started still held stdout, the protocol reader was blocked in readline and held that stream's lock, so close() waited until the child exited (about 20 seconds in the reproduction).
close() now joins the reader with its existing timeout first. If the reader is still blocked, stdout is not closed, and close() raises executor output is still held open by a surviving process. Otherwise stdout closes as before. The reader is a daemon thread and ends when the surviving process exits.
Known limit: the surviving child is not terminated. Containing it (for example with a Windows job object) is a separate change.
Test
ExecutorCloseTests.test_close_is_bounded_when_a_surviving_child_holds_executor_stdout runs a real wrapper that exits after starting a child that inherits stdout. Before the change, close() blocked about 20 seconds and returned without error. After the change it raises within the bound. The test kills the surviving child in a finally block.
python -B -m unittest test_adversarial: 71 tests, OK (6 skipped, as before).
ruff check and ruff format --check are clean for both files.
Not run: the full nvx.py verify, which needs the OpenVMM submodule, and hardware-backed campaigns.
Accept BrokenPipeError when wrapper exits before request flushes
scripts/test_adversarial.py:2638
On POSIX, the wrapper can exit before request() flushes its write. The shell redirects the background child's stdin to /dev/null, so the flush fails and leaves buffered data; _stdin.close() then raises BrokenPipeError. close() preserves that error rather than replacing it with ScriptError, even though cleanup finishes within two seconds. Accept BrokenPipeError here too, while retaining the elapsed-time assertion, so this valid scheduling order does not fail the regression test.
ExecutorClient.close() closed the buffered stdout stream after the wrapper had
exited. If a child process it started still held stdout, the protocol reader was
blocked in readline and holds the buffered stream's lock, so close waited until
that child exited. The reader join with its timeout ran only after the close.
Join the reader first. If it is still blocked, do not close stdout; report that
a surviving process holds the executor output. The reader is a daemon thread and
ends when that process exits.
Adds a real-process regression test in which the wrapper exits and leaves a
child holding stdout. Before the change, close blocked for the child's full
lifetime and returned without error.
Fixesmicrosoft#503 (executor close blocks on inherited stdout)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Signed-off-by: jay23606 <4602056+jay23606@users.noreply.github.com>
When close gives up on a surviving process, the reader kept queueing its output
for a client that no longer reads, and stdout was closed only on the synchronous
path, so a deferred close never released the pipe.
Once close abandons the reader, the reader drains and discards what the surviving
process writes, and the reader closes stdout when its output ends.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Signed-off-by: jay23606 <4602056+jay23606@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #503 (executor close blocks on inherited stdout).
This replaces #532, which was closed. This branch is a single commit on current
dev, with no merge commits.Summary
ExecutorClient.close()closed the buffered stdout stream after the wrapper exited. If a child it started still held stdout, the protocol reader was blocked inreadlineand held that stream's lock, soclose()waited until the child exited (about 20 seconds in the reproduction).close()now joins the reader with its existing timeout first. If the reader is still blocked, stdout is not closed, andclose()raisesexecutor output is still held open by a surviving process. Otherwise stdout closes as before. The reader is a daemon thread and ends when the surviving process exits.Test
ExecutorCloseTests.test_close_is_bounded_when_a_surviving_child_holds_executor_stdoutruns a real wrapper that exits after starting a child that inherits stdout. Before the change,close()blocked about 20 seconds and returned without error. After the change it raises within the bound. The test kills the surviving child in afinallyblock.python -B -m unittest test_adversarial: 71 tests, OK (6 skipped, as before).ruff checkandruff format --checkare clean for both files.nvx.py verify, which needs the OpenVMM submodule, and hardware-backed campaigns.🤖 Generated with Claude Code