Mixing file IO and signals
Paul LeoNerd Evans
leonerd at leonerd.org.uk
Tue Oct 2 14:16:12 BST 2007
I'm attempting to write a single-thread async. IO program, that is
trying to perform the "Holy Trinity" of async. behaviour... That is, to
handle file IO, signals, and timeouts all at once.
Traditionally, file IO and signals are hard to safely mix. My standard
solution in C code is to hold a pipe(), which the select() or poll()
loop tests for readability, and the signal handler writes signal numbers
into. This guarantees that a select() or poll() loop won't block if a
signal is "pending".
My problem is that Perl is somewhat getting in the way of these. Because
of the safe signal mechanism, when a real SIGCHLD (as is my case)
arrives, Perl simply notes that it has arrived, and will handle it later
in the PERL_ASYNC_CHECK() macro. If that arrives during the XS code that
implements the _poll() function, before the poll() syscall, my real
signal handler won't get called, so the pipe won't get written to, so
the poll() syscall will block. And block it does, causing a timeout,
only after which do I finally get my signal.
Some other solutions to this come to mind:
Solution 1: Unsafe signals
This involves using POSIX::sigaction to install an unsafe signal
handler to ensure the pipe gets written to when the signal arrives,
rather than waiting for PERL_ASYNC_CHECK. I can't think of an easy
way to do this safely, because it would at least involve a
POSIX::write() which has the side effect of modifying $!. I can't just
local'ise it during the handler, because that allocates memory.
Perhaps the pad of the signal handler closure can help?
my $dollarbang_save = 0;
my $signum_str = pack( "C", $signum );
my $sa = POSIX::SigAction->new( sub {
$dollarbang_save = $!;
POSIX::write( $sigpipe_write_fileno, $signum_str );
$! = $dollarbang_save;
}, $sigset );
$sa->safe( 0 );
POSIX::sigaction( $signum, $sa );
I don't know enough about unsafe signals to know if that's "safe" to
do there - perhaps someone can advise? If this isn't safe and can't be
made safe, perhaps an XS function written in C could stand in for the
signal handler; this though would have a downside of requiring a C
compiler, and breaking the "pureperl"-ness of my current work.
Solution 2: Linux-specific ppoll()
On Linux, there's a syscall ppoll(), which atomically replaces the
signal process mask with one specified by the call, waits as poll()
would, then switches the mask back. The usual way to use this is to
have normally blocked all the signals you care about, then use ppoll()
to enable them just during the poll(). This avoids the race condition
when the might occur at some other time.
Using ppoll() makes it safe to mix a poll()-like call with regular
flag-based checking of whether signals have arrived. This then avoids
the need for the signal pipe.
This has the downside of being Linux-specific though. [1].
Solution 3: SIGIO
An alternative to the signal pipes, is to turn the code upsidedown,
and use SIGIO for filehandle notification, and use sigsuspend() as the
primary blocking primative. Rather than calling select() or poll(),
you use the SIGIO signal to inform when IO operations can be performed
on filehandles.
In a Linux C program, you can install a signal handler using
sigaction() that includes the SA_SIGINFO flag, and when it is fired,
the handler is passed a struct siginfo_t* which contains information
about the signal (such as the file descriptor and type of IO
available, in SIGIO's case). Or alternatively you can call
sigwaitinfo() or sigtimedwait(), which return the siginfo_t* to you
anyway. These are both unfortunately Linux-specific.
To use SIGIO anywhere outside of Linux, you have to only use it as a
notification that some file IO might be possible, and call poll() or
select() with a zero timeout. You then install an alarm() timer if you
want timeouts, then call sigsuspend(). When it comes back, see what
signals you got. SIGALRM means timeout, SIGIO means you do the usual
select/poll call, any other signal stands as itself. Seems a little
messy, but it probably solves the problem.
Alternatively, are there any other solutions anyone might be able to
suggest?
-----
[1]: As a side note, I may at some point anyway implement an
IO::Poll-like class, IO::Ppoll, for use on Linux. It would have an
interface compatible with IO::Poll, so like IO::Epoll, can be a
dropin replacement. It would though support an additional method,
->sigmask, which would be a way to specify the signal mask to pass
to the ppoll() syscall.
--
Paul "LeoNerd" Evans
leonerd at leonerd.org.uk
ICQ# 4135350 | Registered Linux# 179460
http://www.leonerd.org.uk/
More information about the london.pm
mailing list