Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle SIGPIPE in click.echo(). #626

Closed
wants to merge 6 commits into from
Closed

Handle SIGPIPE in click.echo(). #626

wants to merge 6 commits into from

Conversation

ob
Copy link
Contributor

@ob ob commented Aug 3, 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.

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
Copy link
Contributor Author

ob commented Sep 12, 2016

Any interest in this fix?

@@ -1,5 +1,6 @@
import os
import sys
import errno

This comment was marked as off-topic.

@ThiefMaster
Copy link
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
Copy link
Contributor Author

ob commented Sep 12, 2016

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
Member

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

@untitaker
Copy link
Contributor

untitaker commented Sep 12, 2016

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
Copy link
Contributor Author

ob commented Sep 12, 2016

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
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
Copy link
Contributor Author

ob commented Sep 12, 2016

Pushed something better...

@ob
Copy link
Contributor Author

ob commented Sep 12, 2016

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

@ob
Copy link
Contributor Author

ob commented Sep 12, 2016

@untitaker
Copy link
Contributor

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

@ob
Copy link
Contributor Author

ob commented Sep 12, 2016

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
Contributor

I think we're supposed to raise Abort.

@ob
Copy link
Contributor Author

ob commented Sep 12, 2016

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
Contributor

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

@ob
Copy link
Contributor Author

ob commented Sep 12, 2016

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

@ob
Copy link
Contributor Author

ob commented Sep 16, 2016

Is this acceptable now?

@untitaker
Copy link
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
Copy link
Contributor

sirosen commented Sep 17, 2016

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
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
Contributor

Thanks! Please add yourself to AUTHORS if you want!

@untitaker untitaker closed this Sep 19, 2016
@sirosen
Copy link
Contributor

sirosen commented Sep 19, 2016

@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
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
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
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
Contributor

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

@untitaker
Copy link
Contributor

untitaker commented Sep 20, 2017 via email

@mitsuhiko
Copy link
Contributor

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

@untitaker
Copy link
Contributor

untitaker commented Sep 20, 2017 via email

@mitsuhiko
Copy link
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
Copy link
Contributor

untitaker commented Sep 21, 2017 via email

@mitsuhiko
Copy link
Contributor

We could also raise a subclass of IOError.

@untitaker
Copy link
Contributor

untitaker commented Sep 23, 2017 via email

@mitsuhiko
Copy link
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
Copy link
Contributor

untitaker commented Sep 23, 2017 via email

@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