[Openmcl-devel] Bug when writing to broken pipe?

Ron Garret ron at flownet.com
Mon Dec 8 11:18:13 PST 2014


On Dec 8, 2014, at 12:47 AM, Gary Byers <gb at clozure.com> wrote:

> On 12/02/2014 10:06:09 AM, Ron Garret wrote:
>> This has nothing to do with your code, or even save-application.  CCL just doesn’t handle a broken pipe on stdout gracefully.  You can reproduce this problem at the command line.  Try:
>> ccl -=b e "(dotimes (i 1000) (print i))" | head
>> rg
>> '
> 
> It doesn't gracefully handle
> 
> ccl -b -e '(close *terminal-io*)'
> 
> either, though it might be quietly hysterical instead of as loudly hysterical as it is in Andreas' test case.

I tried this.  The result was not at all what I expected:

Welcome to Clozure Common Lisp Version 1.11-dev-r16306M-trunk  (DarwinX8664)!
? (print 123)
(print 456)

123 
123
? (print 789)

456 
456
? ^D
789 
789
? ^D
[ron at mighty:~]➔

In fact, I am at a total loss to explain this behavior.  (And I also see no way to reconcile it with both the ANSI spec and Occam’s razor.)

> A broken pipe on (a file descriptor associated with) *STANDARD-OUTPUT* is a fairly severe condition; it means that the process on the other end of the pipe has died unexpectedly.

Not necessarily.  The unix ‘head’, ‘more’ and ‘less’ utilities all work this way by design.

> There's no way to resurrect the dead process and reconnect it to the pipe; you could (maybe) somehow try to figure out some way to proceed in some cases, but I really think that this is more difficult to do than it may appear to be at first glance.  The most reasonable things that I can think of generally involve (a) trying to say "a broken pipe error occurred" via the most reliable means that you can and (b) terminate the process abruptly.  That happens to be what happens to most programs that try to do I/O on a broken pipe on most/all Unixy systems: the process gets a SIGPIPE signal that is fatal unless handled (and IIRC largely impractical to handle in any reasonable or graceful way.)  CCL's kernel startup code says that the process wants to ignore SIGPIPE signals, and when those signals are ignored the OS will cause any I/O operation on a broken pipe to return an error code (EPIPE, IIRC).  If you wanted to revert to the default behavior (getting a fatal SIGPIPE signal on any broken pipe), you can do that via
> 
> ? (#_signal #$SIGPIPE #$SIG_DFL)
> 
> and I really think that that may be as good as anything in cases like the one reported here.
> 
> Abruptly killing the CCL process doesn't sound like a very lisp-like way to handle an I/O error,

It isn’t.  That’s why it should only happen if the user has specifically requested such behavior.  (But if the user has specifically requested it, then it should happen.)

> but it the lisp-like way may assume that it's OK to write to streams based on fd #1.  The -b option can help to ensure that the output side of *TERMINAL-IO* is disjoint from *STANDARD-OUTPUT* and is instead based on the "controlling TTY", but this can only work if there is such a TTY (there generally isn't, for instance, if running in an Emacs shell buffer.)

Maybe we have a fundamental disconnect here: isn’t that the whole point of the -b option, so that you can run programs written in CCL in contexts where it is either impossible or undesirable to be lispy (or REPL-y?)

> I'm not sure that it's a good option, but I agree that it should be an option to use the :error-handler argument to SAVE-APPLICATION here (that might work or might not; for the curious, the reason that the option doesn't work is the same reason it didn't work in MCL, and it's not a good reason.)  The possible advantage of quitting instead of just dying from SIGPIPE is that quitting will try to reset lisp threads; the possible disadvatage is that that cleanup code may try to  write to some stream with a broken pipe under it.
> 
> Grace may be in the eye of  the beholder, but none of this sounds very graceful to me.

IMHO the Right Thing here is to change the -b option so that it works as advertised: ccl quits on any attempt to enter a break loop, full stop.  Do not pass go, do not collect $200, and do not produce any additional output.  Failing that, add a new command line option that does this, because it is very useful behavior in certain contexts (like using CCL to write unix command line utilities).

rg




More information about the Openmcl-devel mailing list