[net.bugs.4bsd] ioctl not terminated by SIGALRM

earle@smeagol.UUCP (Greg Earle) (01/10/86)

I just came across something that is aggravating.  While implementing
an XMODEM program for transfers between PC's (PC-TALK) and Suns, I
encountered this:

My first method of dealing with reading from the transmitter (PC)
with a timeout specified was to
        alarm(TIMEOUT_PERIOD);
        n = read(...);
        alarm(0);       /* turn off alarm when read returns */
Note that this had to be ported to a System V machine, so I didn't use
select().  To avoid read overhead, I read a 'sector' at a time, and the
read routine dishes out the chars one at a time to the caller.
This consideration meant that if the read of the entire sector timed out,
I would have to NAK the sender and retransmit the block.
I found that there were a LOT of re-transmit requests, apparently because the
Sun could suck it in faster than the PC could dish it out (A lot of reads
of < sector size #'s of chars.).  I decided a neat way to throttle the Sun
and reasonably ensure that minimal reads would occur would be to use the
BSD-ism ioctl, FIONREAD.  I implemented a while loop to 'poll' the device
via this ioctl until the FIONREAD said that the necessary number of chars to
read was ready in the input stream.  Then comes the read(), and voila -
everything's wonderful.  Minimal retries due to bad reads.

Now here's the rub.  Unlike read(), which is supposed to return if you
catch the alarm SIGALRM, ioctl WON'T.  Thus, if for any reason a
condition exists which causes the xmitter to time out (as seen by
receiver; e.g. if I start the receiver first, before the xmitter,
receiver will time out after sending the first NAK), the alarm signal
will be delivered while inside the
while-FIONREAD-returns-<-SECSIZ-do-ioctl-again loop.  Since the ioctl
doesn't terminate on the signal, it gets ignored (the signal) and the ioctl
just sits there and loops away ...

I am merely suggesting here that I think it would be good if ioctl would
emulate read()'s behavior w. respect to these signals - return -1 and errno =
EINTR.  I have seen other instances where an ioctl on a line attached to
a PC will hang if the PC is not running a program that opens the COM port
(thus pulling up whatever signals it is that UNIX wants to see on the other
end of the line you are ioctl'ing), and it would be helpful here, as well.

STOP PRESS - another reason I can't use just an alarm(n)-read()-alarm(0)
cycle is because of this statement, in signal(2) [Sun] :
"If a caught signal occurs during certain system calls, causing the call to
terminate prematurely, the call is AUTOMATICALLY RESTARTED. [emphasis MINE]
In particular this can occur during a 'read' or 'write(2)' on a slow device
(such as a terminal; " etc etc.

So much for detecting read timeouts.  Thanks a whole lot.

So...
        (1) Is there any way to disable this 'feature' of restarting the call?
        (2) If I set the line to non-blocking mode, so the read won't block,
        will read return -1 errno=EINTR if by some miracle the alarm signal is
        received during the read? (I assume the read will be way too fast in
        returning to ever timeout, if it's non-blocking)
        (3) Is there any way to do what I want, other than some gross hack
        like doing a setjmp() between the ioctl(FIONREAD) and the read(),
        with the handler doing the longjmp()??

Argghh.....

As always, if there is something I overlooked that makes this impractical
or completely ridiculous, feel free to correct.

        Greg Earle
        JPL Spacecraft Data Systems group
        sdcrdcf!smeagol!earle                   (UUCP)
        ia-sun2!smeagol!earle@cit-vax.arpa      (ARPA)

guy@sun.uucp (Guy Harris) (01/13/86)

> Now here's the rub.  Unlike read(), which is supposed to return if you 
> catch the alarm SIGALRM,

Not on 4.2BSD!  (See below)

> ioctl WON'T.

And shouldn't have to.  If an "ioctl" call doesn't block (which FIONREAD
doesn't), the signal will get delivered as soon as the call completes, so
the alarm will get delivered when you leave the FIONREAD.  (Yes, it does - I
just tried it.)

> ...I have seen other instances where an ioctl on a line attached to 
> a PC will hang if the PC is not running a program that opens the COM port
> (thus pulling up whatever signals it is that UNIX wants to see on the other
> end of the line you are ioctl'ing), and it would be helpful here, as well.

UNIX doesn't look for signals on an "ioctl" (I presume you're referring to
modem control signals, like DCD, here); it does look for them on an "open".

> STOP PRESS - another reason I can't use just an alarm(n)-read()-alarm(0)
> cycle is because of this statement, in signal(2) [Sun] :
> "If a caught signal occurs during certain system calls, causing the call to
> terminate prematurely, the call is AUTOMATICALLY RESTARTED. [emphasis MINE]
> In particular this can occur during a 'read' or 'write(2)' on a slow device
> (such as a terminal; " etc etc.
> 
> So much for detecting read timeouts.  Thanks a whole lot.

Don't thank us, thank the people at Berkeley.

> So...
> 	(1) Is there any way to disable this 'feature' of restarting the call?

Not in 4.2BSD.  4.3BSD permits you to specify that a particular signal will
interrupt rather than restart a system call.  (Sun UNIX is not currently
based on 4.3BSD - for one thing, 4.3BSD isn't out yet...)

> 	(2) If I set the line to non-blocking mode, so the read won't block,
> 	will read return -1 errno=EINTR if by some miracle the alarm signal is
> 	received during the read? (I assume the read will be way too fast in 
> 	returning to ever timeout, if it's non-blocking)

As discussed above for "ioctl", read won't return EINTR because it won't
block.  The SIGALRM will come in after "read" returns.  (EINTR is returned
*ONLY* if a system call was blocked waiting for a "slow event", like a
"read" or "write" on a slow device or a "wait".)

> 	(3) Is there any way to do what I want, other than some gross hack
> 	like doing a setjmp() between the ioctl(FIONREAD) and the read(),
> 	with the handler doing the longjmp()??

No.  The bit with setjmp/longjmp is the standard way of doing that sort of
thing on 4.2BSD.

	Guy Harris