Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python socket recv() and signals

i have a simple (non-threaded) script that listens on a socket for data, analyses it and uses internally SIGALRM's to send emails at predefined timer internals.

the problem is during the recv() loop, the occurrence of the SIGALRM appears to raise a

socket.error: [Errno 4] Interrupted system call

and hence terminating the program.

i can wrap the recv() with a try/except block, but i was wondering whether i would loose any data during this time, or whether the buffer will prevent loss.

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((host, port))
while True:
    try:
        data = s.recv(2048)
    except socket.error, e:
        pass
    yield data
s.close()
return
like image 241
yee379 Avatar asked Apr 18 '13 23:04

yee379


1 Answers

The standard way to handle this in C is to loop around EINTR. And, while that shouldn't be necessary in Python, it is.

Your code is pretty close to the idiomatic way to deal with this, except for two things:

  • You don't want to ignore all errors, just EINTR.
  • You can't yield data after ignoring an error that way, because you'll re-yield the previous packet (if there is one) or raise a NameError (if this is the first time through the loop).

So:

while True:
    try:
        data = s.recv(2048)
    except socket.error, e:
        if e.errno != errno.EINTR:
            raise
    else:
        yield data

So, why do you have to do this?

POSIX allows almost any syscall to return EINTR for certain kinds of temporary failure—which includes being interrupted by a signal. Many POSIX platforms do this. The expected application behavior is to just retry (if you were trying a blocking call) or go back around the loop (if you're inside a level-triggered reactor). This blog post gives a pretty good explanation for why POSIX works this way. (It's a justification after the fact, and definitely not the actual rationale…) Also see the glibc documentation.

Like most scripting languages, Python is supposed to wrap all EINTR-prone calls internally, so you shouldn't ever have to think about this (unless you're using third-party C extensions). But unfortunately, it has bugs. The most recent set of cases that were found and fixed are in issue 9867 and issue 12268.

Even if they've finally caught everything, that only helps if you can depend on a sufficiently new version of Python. Given that you're using pre-2.6-style except syntax, and the latest fixes went in to some 2.7.x and 3.2.x bugfix release, that presumably doesn't work for you.


There are other ways to solve this problem, but they're more complicated and less portable. For example, you can replace your blocking recv with a blocking pselect and a non-blocking recv, add a pipe into the fd set along with the socket, replace all of your signal handlers with functions that just write (one byte) to that pipe, and move the actual signal-handling code into the event loop. Then, on some platforms, you will never get an EINTR. But this probably isn't the approach you want to take in Python.

like image 171
abarnert Avatar answered Nov 09 '22 09:11

abarnert