I am writing a program in python. I wish to read from stdin, and handle sigchld. I want to handle either input as it comes in, without spinning (speculatively sampling for input).
I can't catch sys-call interrupted by signal on every call I make.
Am I going about this the wrong way? Can I get this to work without try/except?
My main worry is not the try/except I have in the code so far. But all the hundreds I will need in ever other line of code in the program. It does not seem modular to me.
Here is some test code:
#!/usr/bin/python
from time import sleep
import select
import signal
import fcntl
import os
import sys
pipe_r, pipe_w = os.pipe()
flags = fcntl.fcntl(pipe_w, fcntl.F_GETFL, 0)
flags = flags | os.O_NONBLOCK
fcntl.fcntl(pipe_w, fcntl.F_SETFL, flags)
signal.signal(signal.SIGCHLD, lambda x,y: None)
signal.signal(signal.SIGALRM, lambda x,y: None)
signal.siginterrupt(signal.SIGCHLD,False) #makes no difference
signal.siginterrupt(signal.SIGALRM,False) #makes no difference
signal.set_wakeup_fd(pipe_w)
signal.setitimer(signal.ITIMER_REAL, 2, 2)
poller = select.epoll()
poller.register(pipe_r, select.EPOLLIN)
poller.register(sys.stdin, select.EPOLLIN)
print "Main screen turn on"
while True:
events=[]
try:
events = poller.poll()
try:
for fd, flags in events:
ch=os.read(fd, 1)
if fd==pipe_r:
sys.stdout.write( "We get Signal" )
if fd==sys.stdin.fileno():
sys.stdout.write( ch )
sys.stdout.flush()
except IOError as e:
print "exception loop" + str(e)
except IOError as e:
print "exception poll" + str(e)
versions:
#python --version
python 2.7.3
#uname -a
Linux richard 3.2.0-32-generic #51-Ubuntu SMP Wed Sep 26 21:33:09 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux
Yes, you are going about this the right way. (Well, I'd probably use select
or poll
instead of epoll
, because then your code will be portable to non-linux platforms…)
I'm assuming here that polling on two or more fds is inherently necessary to your design. If you've only added the epoll loop to handle signals, then that part isn't necessary; all you need is a simple EINTR loop around the read
call. But, once you have a pipe for other purposes, hooking it up as the signal wakeup pipe is a reasonable thing to do, and then your, and everything else you're doing is implicit in reading from multiple fds.
Either way, you really can't avoid the try
blocks. (This isn't quite true. You can always block signals with sigblock
/sigprocmask
/etc.—they're not in Python's signal
module, but you can always ctypes
them. But then you generally need slightly more code, with a try
/finally
instead of a try
/except
, and your code becomes harder to debug, so there's not much point in going down this road except in the few cases where you're dealing with code that can't be interrupted and have to be sigblocked.)
It's a standard feature of POSIX that many syscalls can be interrupted by signals, in which case they fail with EINTR.
Most particular platforms restrict the list of calls that can be interrupted to a smaller set than POSIX allows, so you'd have to look at the linux docs to be sure exactly what is and isn't EINTR-safe, but the epoll
family of methods, along with read
/recv
/write
/send
and friends, are almost certain to be unsafe.
Using siginterrupt
as you do may help, but it doesn't solve the problem. In particular, no call is required to restart instead of interrupting, and there are certain cases where they're required to interrupt or complete with partial results instead of restarting. I'm pretty sure select
and poll
are never restartable, so there's a good chance the epoll
family isn't either, but you'd have to check. And at any rate, most BSD derivatives have restart on by default, so if linux is the same, you're not changing anything.
Python could have wrapped all of this up with implicit EINTR loops, and I believe some of the higher-level methods (like sendall
) do so, but the lower-level methods do not. But you can wrap things up yourself. For example (off the top of my head, untested):
def dopoll(poller):
while True:
try:
return poller.poll()
except IOError as e:
if e.errno != EINTR:
raise
And then, everywhere you would have called poll
, call dopoll
instead.
I have written a python library, that presents signals as streams, so I can use select/poll/epoll etc with them. It uses libc (Gnu C library) to do the hard work. Hope it is of use.
https://github.com/richard-delorenzi/pysigstream
If you want to improve it then you can fork it, and please send a pull request.