Skip to content

Handle SIGPIPE in click.echo().#626

Closed
ob wants to merge 6 commits into
pallets:masterfrom
ob:master
Closed

Handle SIGPIPE in click.echo().#626
ob wants to merge 6 commits into
pallets:masterfrom
ob:master

Conversation

@ob

@ob ob commented Aug 3, 2016

Copy link
Copy Markdown
Contributor

When the output of a click program is sent to another program via a
pipe, and the pipe is closed prematurely, click.echo() was printing an
exception backtrace instead of silently exiting like most command line
utilities.

Fixes: #625

ob added 3 commits August 3, 2016 10:53
When the output of a click program is sent to another program via a
pipe, and the pipe is closed prematurely, click.echo() was printing an
exception backtrace instead of silently exiting like most command line
utilities.
@ob

ob commented Sep 12, 2016

Copy link
Copy Markdown
Contributor Author

Any interest in this fix?

Comment thread click/utils.py Outdated
@@ -1,5 +1,6 @@
import os
import sys
import errno

This comment was marked as off-topic.

@ThiefMaster

Copy link
Copy Markdown
Member

LGTM, but I'd rather have someone who did some actual click development look over it. General suggestion: Squash those three commits into a single one. The last two just fix issues in the initial one after all, no need for that to be part of the project's permanent history ;)

@ob

ob commented Sep 12, 2016

Copy link
Copy Markdown
Contributor Author

I thought commit squashing was up to whoever merged the branch...

https://github.com/blog/2141-squash-your-commits

I can squash the commits if that is preferable.

@ThiefMaster

Copy link
Copy Markdown
Member

True, we can do it too. Forgot about that ;)

@untitaker

untitaker commented Sep 12, 2016

Copy link
Copy Markdown
Contributor

This doesn't silently exit, it swallows the exception and lets the application continue. IMO SIGPIPE should not be catched on this level but much further up (somewhere near subcommand dispatch) and then the traceback suppressed (but still exiting).

@ob

ob commented Sep 12, 2016

Copy link
Copy Markdown
Contributor Author

Oops... I'm missing a sys.exit() there. I couldn't find a place to add a test so I ended up screwing up the fix ;-/

I'll take a look at subcommand dispatch and see if I can figure out where you mean.

@untitaker

Copy link
Copy Markdown
Contributor

I think here: https://github.com/pallets/click/blob/master/click/core.py#L700

@mitsuhiko Do you agree this is the proper way to handle SIGPIPE, or should the signal module be used?

@ob

ob commented Sep 12, 2016

Copy link
Copy Markdown
Contributor Author

Pushed something better...

@ob

ob commented Sep 12, 2016

Copy link
Copy Markdown
Contributor Author

Ah, and now I read your comment and I see your suggestion is probably better...

@ob

ob commented Sep 12, 2016

Copy link
Copy Markdown
Contributor Author

@untitaker

Copy link
Copy Markdown
Contributor

Yeah, but is sys.exit(EPIPE) the right thing to do?

@ob

ob commented Sep 12, 2016

Copy link
Copy Markdown
Contributor Author

I was just googling that... it seems exiting -1 might be better... I tried a few system utilities and they seem to exit 0. I was about to go read POSIX :)

@untitaker

Copy link
Copy Markdown
Contributor

I think we're supposed to raise Abort.

@ob

ob commented Sep 12, 2016

Copy link
Copy Markdown
Contributor Author

The problem with raising Abort is that it prints "Aborted!" to stderr. This is undesirable for things like piping the output to head for example.

@untitaker

Copy link
Copy Markdown
Contributor

Fair enough, then sys.exit(1) is probably the way to go.

@ob

ob commented Sep 12, 2016

Copy link
Copy Markdown
Contributor Author

Okay, I pushed that. BTW, I'm so counting on you squashing all this mess ;)

@ob

ob commented Sep 16, 2016

Copy link
Copy Markdown
Contributor Author

Is this acceptable now?

@untitaker

Copy link
Copy Markdown
Contributor

I'll probably merge in a few days. It looks fine to me, but code review at
conferences is a bad idea.

On Fri, Sep 16, 2016 at 11:10:14AM -0700, Oscar Bonilla wrote:

Is this acceptable now?

You are receiving this because you commented.
Reply to this email directly or view it on GitHub:
#626 (comment)

@sirosen

sirosen commented Sep 17, 2016

Copy link
Copy Markdown
Contributor

Although I agree that click.echo should capture EPIPE by default, could we add an option somewhere to have it raise Abort or reraise the IOError instead?

I have a bit of code that's doing

try:
    click.echo(message, nl=newline, err=write_to_stderr)
except IOError as err:
    if err.errno is errno.EPIPE:
        pass
    else:
        raise

So that I can maintain my promises about exit codes.

This would be, to me, an acceptable alternative:

try:
    click.echo(message, nl=newline, err=write_to_stderr, handle_epipe=False)
except IOError:
    # a reraised IOError from inside of click.echo! Must be EPIPE
    pass

What I object to is being asked to do this:

try:
    click.echo(message, nl=newline, err=write_to_stderr)
except SystemExit:
    # I guess this was an EPIPE?
    pass

As a caller into click.echo, having it decide to raise SystemExit seems wrong.
Consider:

set -o pipefail
mycommand | head -n 20 | grep 'importantstring'

If supporting a pipeline like that is a priority for mycommand, it shouldn't be deprived of the features of click.echo.

@untitaker

Copy link
Copy Markdown
Contributor

Your code doesn't have to change since SystemExit is raised somewhere completely different than from within echo. The PR title is now wrong.

On 17 September 2016 19:39:29 CEST, Stephen Rosen notifications@github.com wrote:

Although I agree that click.echo should capture EPIPE by default,
could we add an option somewhere to have it raise Abort or reraise
the IOError instead?

I have a bit of code that's doing

try:
   click.echo(message, nl=newline, err=write_to_stderr)
except IOError as err:
   if err.errno is errno.EPIPE:
       pass
   else:
       raise

So that I can maintain my promises about exit codes.

This would be, to me, an acceptable alternative:

try:
click.echo(message, nl=newline, err=write_to_stderr,
handle_epipe=False)
except IOError:
   # a reraised IOError from inside of click.echo! Must be EPIPE
   pass

What I object to is being asked to do this:

try:
   click.echo(message, nl=newline, err=write_to_stderr)
except SystemExit:
   # I guess this was an EPIPE?
   pass

As a caller into click.echo, having it decide to raise SystemExit
seems wrong.
Consider:

set -o pipefail
mycommand | head -n 20 | grep 'importantstring'

If supporting a pipeline like that is a priority for mycommand, it
shouldn't be deprived of the features of click.echo.

You are receiving this because you commented.
Reply to this email directly or view it on GitHub:
#626 (comment)

Sent from my Android device with K-9 Mail. Please excuse my brevity.

untitaker pushed a commit that referenced this pull request Sep 19, 2016
When the output of a click program is sent to another program via a
pipe, and the pipe is closed prematurely, click.echo() was printing an
exception backtrace instead of silently exiting like most command line
utilities.

Fix #626
Fix #625
@untitaker

Copy link
Copy Markdown
Contributor

Thanks! Please add yourself to AUTHORS if you want!

@untitaker untitaker closed this Sep 19, 2016
@sirosen

sirosen commented Sep 19, 2016

Copy link
Copy Markdown
Contributor

@untitaker Thanks for pointing out that this handling is, in fact, safe for callers of click.echo.

Please correct me if I'm wrong, but as I understand it this catches an EPIPE that makes its way up to click.Command.main(), so it handles click.echo(), print(), and anything else that may encounter a broken pipe inside of the running command.
If you want re-title the PR, I'd suggest "Handle EPIPE by exiting silently with status 1".

Anyway, as always, thanks for this excellent CLI toolkit.

@untitaker

Copy link
Copy Markdown
Contributor

Sorry, yes, I forgot to rename the PR and a same-named commit is now in master :(

On Mon, Sep 19, 2016 at 10:25:29AM -0700, Stephen Rosen wrote:

@untitaker Thanks for pointing out that this handling is, in fact, safe for callers of click.echo.

Please correct me if I'm wrong, but as I understand it this catches an EPIPE that makes its way up to click.Command.main(), so it handles click.echo(), print(), and anything else that may encounter a broken pipe inside of the running command.
If you want re-title the PR, I'd suggest "Handle EPIPE by exiting silently with status 1".

Anyway, as always, thanks for this excellent CLI toolkit.

You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#626 (comment)

@mitsuhiko

Copy link
Copy Markdown
Contributor

@untitaker i just discovered this merge and I'm not particularly happy with this because this captures all sigpipes, so even on sockets. Also not sure if click should handle this by default like this. At least maybe make it optional?

@ThiefMaster

Copy link
Copy Markdown
Member

Any way of checking if SIGPIPE was caused by stdin/stdout/stderr? I don't think getting a traceback in these cases is something you'd want

@mitsuhiko

Copy link
Copy Markdown
Contributor

No, not really. You can only catch down individual writes (like in echo) and rethrow it.

@untitaker

untitaker commented Sep 20, 2017 via email

Copy link
Copy Markdown
Contributor

@mitsuhiko

Copy link
Copy Markdown
Contributor

You get a sigpipe when the socket is not connected and you write to it.

@untitaker

untitaker commented Sep 20, 2017 via email

Copy link
Copy Markdown
Contributor

@mitsuhiko

Copy link
Copy Markdown
Contributor

@untitaker what about us wrapping both text and binary streams and augment the exception with from_stdio = True? Then we can capture that only by default.

@untitaker

untitaker commented Sep 21, 2017 via email

Copy link
Copy Markdown
Contributor

@mitsuhiko

Copy link
Copy Markdown
Contributor

We could also raise a subclass of IOError.

@untitaker

untitaker commented Sep 23, 2017 via email

Copy link
Copy Markdown
Contributor

@mitsuhiko

Copy link
Copy Markdown
Contributor

@untitaker the proposal is not to patch stdio but to change our wrappers. We already use wrappers for the unicode support and there are already accessors for getting binary streams. We can trivially further customize those streams.

@untitaker

untitaker commented Sep 23, 2017 via email

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Nov 13, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handling of SIGPIPE

5 participants