Skip to content

fix(server): keep a stdin error handler after cancelling a run - #94

Merged
JacobLinCool merged 2 commits into
wasm-oj:mainfrom
TakalaWang:fix/server-runner-epipe-on-cancel
Oct 8, 2026
Merged

JacobLinCool merged 2 commits into
wasm-oj:mainfrom
TakalaWang:fix/server-runner-epipe-on-cancel

Conversation

@TakalaWang

Copy link
Copy Markdown
Contributor

Why

ServerRunner removes the child stdin error listener in its cleanup for three paths: the runtime preparation stage, native runs and native interactive sessions.

A run can be cancelled, or hit its wall deadline, while the parent is still writing a large request to the child's stdin. The ~15 MB CPython runtime request is one example. When that happens:

  1. The child is killed with SIGKILL.
  2. The pending write fails with EPIPE.
  3. No listener is left to receive the error, so it is emitted unhandled and the whole host process crashes with Uncaught Error: write EPIPE.

A downstream judge (NOJV) hit this under load, when a Python checker run was cancelled at a request deadline. It currently carries a pnpm patch for it.

Change

  • In all three cleanup paths, swap the stdin error listener for a no-op instead of removing it: child.stdin.off("error", onStdinError).on("error", () => undefined).
    • This matches the existing handling of late child error events.
    • Node's child close event does not wait for stdin, so this also covers late errors after a normal settle.
  • ServerCompiler and the Go stage already keep their stdin error listeners for the child's whole lifetime, so they need no change.
  • CHANGELOG.md: entry under Unreleased. This may conflict trivially with feat(runtime): allow Python interactors in interact #93, which adds the same heading.

Verification

  • New test in server-runner-preparation.test.ts, one case per site (preparation, run, interaction): cancel, then emit EPIPE on the child's stdin. All 3 fail on main with 'Error: write EPIPE' was thrown and pass with the fix.
  • Real-process repro (not committed): a real child that never reads stdin, a 32 MiB run request, and a cancel right after spawn. On main the host gets an uncaught Error: write EPIPE; with the fix it passes on 3 of 3 runs.
  • pnpm run typecheck, pnpm run lint, pnpm test (959 passed, 9 skipped) and pnpm run build all pass.

🤖 Generated with Claude Code

ServerRunner's cleanup for the runtime preparation stage, native runs and
native interactive sessions removed the child stdin `error` listener. When a
run is cancelled or hits its wall deadline while the parent is still writing
a large request (for example the ~15 MB CPython runtime request), the child is
SIGKILLed, the pending write fails with EPIPE, and with no listener left the
error is emitted unhandled and crashes the host process with
"Uncaught Error: write EPIPE".

Swap the listener for a no-op instead of dropping it, mirroring the existing
late child `error` handling. ServerCompiler and the Go stage already keep
their stdin error listeners for the child's lifetime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobLinCool
JacobLinCool merged commit 2e37c5e into wasm-oj:main Oct 8, 2026
1 check passed
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