[Haskell-cafe] Printing of asynchronous exceptions to stderr

Richard O'Keefe ok at cs.otago.ac.nz
Fri Dec 17 01:51:08 CET 2010


On 17/12/2010, at 12:03 PM, Mitar wrote:
> 
> Yes, this is the same problem as when designing an operating system:
> what should happen to a system call if it is interrupted. And I agree
> with elegance of simply returning EINTR errno. This makes system calls
> much easier to implement and for user it is really simple to wrap it
> in a loop retrying it. So it is quite often to see such function:
> 
> static int xioctl( int fd, int request, void *arg) {
> int r;
> 
> do {
>   r = ioctl(fd, request, arg);
> } while (-1 == r && EINTR == errno);
> 
> return r;
> }
> 
And _this_ you call "simple elegance"?

It may be simple, but the user has to know to do it, and the user has to
do it over and over and over again.

There are gotchas when this kind of thing propagates into higher layers.
For example, in another window I have the source code for fwrite() from
OpenSolaris.  (Actually, there are three fwrite.c files, and I'm not at
all sure that the one I'm looking at, /usr/src/lib/libc/port/stdio/fwrite.c,
is the correct one.)  In the _IONBF case there are loops that look like

	while ((n = write(fileno(iop), dptr, s)) != s) {
	    if (n == -1) {
		if (!cancel_active()) iop->_flag |= _IOERR;
		return written/size;
	    } else {
		dptr += n;
		s -= n;
		written += n;
	    }
	}

Do you see an EINTR loop there?  Neither do I.  This means
that it is possible for fwrite() to return -1 with ferror()
true and errno == EINTR.  If you are writing to the ANSI C
standard, there is no way you can respond appropriately to
this because EINTR is not part of the C standard.

It _is_ part of the POSIX standard that file operations are
defined to set errno when they go wrong.  However, there is a
really nasty gotcha here.  The loop

	do {
	    r = fwrite(p, sizeof *p, n, output);
	} while (r == 0 && errno == EINTR);

is simply wrong.  There may have been successful partial
writes before the final interrupted one.  So let's try again.
r is the number of complete items written.  If the write is
interrupted, this need not be zero.  So

	for (;;) {
	    r = fwrite(p, sizeof *p, n, output);
	    if (!ferror(output)) return SUCCESS;
	    if (errno != EINTR) return FAILURE;
	    p += r;
	    n -= r;
	}

Not quite so easy to write.  I may have made any number of
mistakes, but it doesn't matter, because this still has a
problem.  fwrite() returns the number of complete items
written (written/size), but in the case of a partial write,
it may have written *more* than that.

Normally this doesn't matter, because almost every possible
error from fwrite() is something you would not want to
continue.  The only two you might want to continue are
EAGAIN and EINTR.  EAGAIN can only arise if you've explicitly
asked for non-blocking I/O, which you can't with fopen().

Now I'm not saying that you _couldn't_ design something like
fwrite() that _would_ be resumable after EINTR.  My point is
that apparently cute hacks like EINTR have a nasty way of
propagating into higher level interfaces and breaking them.





More information about the Haskell-Cafe mailing list