Skip to content

fix(harness): prevent pipe deadlock in LocalFilesystemWithShell.execute - #2839

Open
zzz-ghost wants to merge 1 commit into
agentscope-ai:mainfrom
zzz-ghost:fix/2838-execute-pipe-deadlock
Open

fix(harness): prevent pipe deadlock in LocalFilesystemWithShell.execute#2839
zzz-ghost wants to merge 1 commit into
agentscope-ai:mainfrom
zzz-ghost:fix/2838-execute-pipe-deadlock

Conversation

@zzz-ghost

Copy link
Copy Markdown

What this PR does

Fixes a pipe deadlock in LocalFilesystemWithShell.execute(): the child process's stdout/stderr are now drained on daemon threads concurrently with Process.waitFor(), instead of only after it returns.

Root cause

Previously execute() called Process.waitFor(timeout) first and read getInputStream() / getErrorStream() only afterwards. If the child wrote more than the OS pipe buffer (≈4 KB on Windows, 64 KB default on Linux), it blocked in write() forever while the parent blocked in waitFor() — a classic pipe deadlock. Every such command was misreported as a timeout (exit 124) and forcibly killed, even though it would have finished in well under a second.

Fix

  • Start two daemon drainer threads (8 KB chunks into ByteArrayOutputStream) immediately after ProcessBuilder.start(), then waitFor(timeout), then join the drainers — destroying the process first on timeout so the drainers terminate.
  • This mirrors the fix already applied to ShellCommandTool in agentscope-core ("Start asynchronous stream readers immediately to prevent pipe buffer deadlock"), which LocalFilesystemWithShell was missed by.
  • Behavior is otherwise unchanged: same timeout messages, exit code 124 semantics, charset selection, and maxOutputBytes truncation.

Testing

New regression test execute_outputLargerThanOsPipeBufferCompletesWithoutDeadlock prints ~70 KB via shell builtins (works on both sh and cmd.exe, no Python needed):

Before fix After fix
Result deadlock → killed after 60 s, exit 124 completes in 0.4 s, exit 0
Output all 4000 lines captured, not truncated

Verified on Windows 10 (≈4 KB pipe buffer); the threshold (68–72 KB) also exceeds the 64 KB Linux default pipe buffer, so the test covers both platforms.

Fixes #2838

@CLAassistant

CLAassistant commented Aug 25, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Drain stdout/stderr on daemon threads concurrently with Process.waitFor.
Previously a child writing more than the OS pipe buffer (~4 KB on Windows,
64 KB on Linux) blocked in write() while the parent blocked in waitFor(),
deadlocking until the command was forcibly killed and misreported as a
timeout (exit 124).

Mirrors the fix already applied to ShellCommandTool in agentscope-core.
Adds a regression test that prints ~70 KB from the shell and asserts the
command completes with exit 0 and full output.

Fixes agentscope-ai#2838
@zzz-ghost
zzz-ghost force-pushed the fix/2838-execute-pipe-deadlock branch from f8ae46b to 7fb5e9a Compare August 25, 2026 08:17
@zzz-ghost zzz-ghost closed this Aug 25, 2026
@zzz-ghost zzz-ghost reopened this Aug 25, 2026
@zzz-ghost zzz-ghost closed this Aug 25, 2026
@zzz-ghost zzz-ghost reopened this Aug 25, 2026
@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ent/filesystem/local/LocalFilesystemWithShell.java 80.00% 4 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@zzz-ghost

Copy link
Copy Markdown
Author

Note: the two earlier build (ubuntu-latest) failures on this PR were a pre-existing flaky
test, unrelated to this change — HarnessAgentDynamicHookBuilderTest failed in different
methods each run with JUnitException: Failed to close extension context
(DirectoryNotEmptyException on the JUnit @TempDir). It looks like a race between JUnit
temp-dir cleanup and the framework's fire-and-forget session/transcript mirror writes;
HarnessAgent.close() documents this race, but these tests never close the agent.
CI is green after re-running. Happy to file a separate issue for the flaky test if you'd like.

@dailingtao dailingtao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM cc @jujn

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.

[Bug]: LocalFilesystemWithShell.execute deadlocks when child process output exceeds pipe buffer (waitFor before draining stdout/stderr)

3 participants