Skip to content

Handle broken pipe on Windows (non-Unix) gracefully - #1069

Closed
Mathjk wants to merge 1 commit into
Wilfred:masterfrom
Mathjk:fix/windows-broken-pipe
Closed

Mathjk wants to merge 1 commit into
Wilfred:masterfrom
Mathjk:fix/windows-broken-pipe

Conversation

@Mathjk

@Mathjk Mathjk commented Sep 30, 2026

Copy link
Copy Markdown

Fixes #1068

Summary

reset_sigpipe is a no-op on Windows (#[cfg(unix)]), so when the
process reading difftastic's stdout exits early — difft a b | head -2 —
every println! path panics:

thread 'main' panicked at library/std/src/io/stdio.rs:
failed printing to stdout: The pipe is being closed. (os error 232)

On Unix the same invocation dies quietly to SIGPIPE (exit 141). This PR
makes the non-Unix path match that behavior.

Approach

print!/println! panic inside std on write errors, so there is no
interceptable choke point. Instead this threads a single
&mut impl io::Write (a StdoutLock owned by main) through the
~47 output sites across the display and parse modules, all now returning
io::Result<()>:

  • main() splits into run(mode, out) -> io::Result<()>; the
    wrapper maps ErrorKind::BrokenPipe to process::exit(EXIT_SUCCESS)
    and keeps the identical panic message for other write errors
    (parity with std's failed printing to stdout message).
  • A plain StdoutLock is used (not BufWriter) so process::exit
    paths can never lose buffered output. As a side benefit there is now
    one lock acquisition per result instead of per line.
  • Latent fix: the parallel dir-diff sender thread did
    .expect(\"Receiver should be connected\"); once the receiver can
    drop early (on a ? bail) that would panic. It now ignores the send
    error with an explanatory comment — only reachable when we are already
    erroring out.

stderr output (eprintln!) is intentionally unchanged — panic on a
closed stderr is the status quo on all platforms.

Verification

  • Every output mode tested against a reader that exits immediately:
    side-by-side, inline, --display=json, --dump-syntax,
    --dump-ts, --list-languages, dir diffs, git unmerged mode —
    all quiet exit 0 where they previously panicked (verified by
    neutralizing the SIGPIPE reset so the Unix build takes the Windows
    code path exactly).
  • Real Windows binary (x86_64-pc-windows-gnu, run on Windows 11):
    difft a.py b.py | Select-Object -First 0 and -First 2 exit 0;
    normal output unchanged.
  • Output is byte-identical to master across 14 captures (side-by-side,
    inline, json, dump modes, list-languages, exit codes).
  • cargo fmt --check clean; clippy shows only pre-existing warnings;
    cargo test: 123 unit + 24 CLI tests pass; cargo check --target x86_64-pc-windows-gnu clean.

Disclosure

Implemented with AI assistance (Devin); the change was reviewed by the
account owner, and verified as described above. See issue #1068.

println!() panics when a write fails. reset_sigpipe() handles this on
Unix by dying on SIGPIPE, but on platforms without SIGPIPE (e.g.
Windows) piping difft's output to a process that exits early crashed
with "failed printing to stdout".

Thread an io::Result through the display functions so write errors
propagate to main, and exit silently on BrokenPipe, mirroring the
SIGPIPE termination on Unix. Other write errors still panic, as they
did before.

This also makes display slightly faster, as we hold a single locked
stdout for the duration of printing instead of locking it on every
println!.

Developed with AI assistance.
@Wilfred Wilfred closed this Sep 30, 2026
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.

Panic when output pipe closes early on Windows

2 participants