Skip to content

interp: dup pipe fds in pipelines so EOF and SIGPIPE propagate#1334

Open
qiangli wants to merge 1 commit into
mvdan:masterfrom
qiangli:interp-pipe-fd-eof
Open

interp: dup pipe fds in pipelines so EOF and SIGPIPE propagate#1334
qiangli wants to merge 1 commit into
mvdan:masterfrom
qiangli:interp-pipe-fd-eof

Conversation

@qiangli
Copy link
Copy Markdown

@qiangli qiangli commented May 9, 2026

Summary

When the parent Go process holds extra references to a pipeline's read or write end, EOF does not propagate to readers and SIGPIPE is not delivered to writers. Concretely, yes | head -1 hangs forever instead of terminating after head closes its stdin.

Approach

Duplicate both pipe ends with syscall.Dup before handing them to the goroutines that run the left and right sides, then immediately close the originals in the parent. The parent no longer holds any reference to the pipe; closing the duplicates in the goroutines is now sufficient to drive EOF/SIGPIPE through the pipeline.

  • Duplicates are marked CloseOnExec so external commands spawned from inside the goroutines do not inherit them.
  • Restoring r.stdin from oldStdin after wg.Wait() prevents the right-hand side's stdin replacement from leaking into subsequent statements in the enclosing block.
  • A new helper dupPipeFd lives in interp/os_unix.go on unix platforms and as a no-op fallback in interp/os_notunix.go. On non-unix the previous behaviour is preserved (pipelines run but EOF/SIGPIPE propagation is best-effort).

Fixes #1142.

Test plan

  • go test -short ./interp/ passes.
  • Manual: yes | head -1 exits promptly under the patched runner; previously hung indefinitely.

When the parent Go process holds extra references to a pipeline's
read or write end, EOF does not propagate to readers and SIGPIPE
is not delivered to writers. Concretely, `yes | head -1` hangs
forever instead of terminating after head closes its stdin.

Fix this by duplicating both pipe ends with syscall.Dup before
handing them to the goroutines that run the left and right sides,
then immediately closing the originals in the parent. The parent
no longer holds any reference to the pipe; closing the duplicates
in the goroutines is now sufficient to drive EOF/SIGPIPE through
the pipeline.

The duplicate fds are marked CloseOnExec so external commands
spawned from inside the goroutines do not inherit them.

Restoring r.stdin from oldStdin after wg.Wait() prevents the
right-hand side's stdin replacement from leaking into subsequent
statements in the enclosing block.

A new helper, dupPipeFd, lives in interp/os_unix.go on unix
platforms and as a no-op fallback in interp/os_notunix.go. On
non-unix the previous behaviour is preserved (pipelines run but
EOF/SIGPIPE propagation is best-effort).

Fixes mvdan#1142
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.

pipelining and redirection are broken because gosh does not close file descriptors

1 participant