Skip to content

Commit

Permalink
Use signal.set_wakeup_fd in IOLoop to close a race with signal handlers.
Browse files Browse the repository at this point in the history
  • Loading branch information
bdarnell committed Sep 17, 2012
1 parent 200c607 commit 18af6c7
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 1 deletion.
32 changes: 32 additions & 0 deletions tornado/ioloop.py
Expand Up @@ -269,6 +269,36 @@ def start(self):
return
self._thread_ident = thread.get_ident()
self._running = True

# signal.set_wakeup_fd closes a race condition in event loops:
# a signal may arrive at the beginning of select/poll/etc
# before it goes into its interruptible sleep, so the signal
# will be consumed without waking the select. The solution is
# for the (C, synchronous) signal handler to write to a pipe,
# which will then be seen by select.
#
# In python's signal handling semantics, this only matters on the
# main thread (fortunately, set_wakeup_fd only works on the main
# thread and will raise a ValueError otherwise).
#
# If someone has already set a wakeup fd, we don't want to
# disturb it. This is an issue for twisted, which does its
# SIGCHILD processing in response to its own wakeup fd being
# written to. As long as the wakeup fd is registered on the IOLoop,
# the loop will still wake up and everything should work.
old_wakeup_fd = None
if hasattr(signal, 'set_wakeup_fd'): # requires python 2.6+, unix
try:
old_wakeup_fd = signal.set_wakeup_fd(self._waker.write_fileno())
except ValueError: # non-main thread
pass
if old_wakeup_fd != -1:
# Already set, restore previous value. This is a little racy,
# but there's no clean get_wakeup_fd and in real use the
# IOLoop is just started once at the beginning.
signal.set_wakeup_fd(old_wakeup_fd)
old_wakeup_fd = None

while True:
poll_timeout = 3600.0

Expand Down Expand Up @@ -349,6 +379,8 @@ def start(self):
self._stopped = False
if self._blocking_signal_threshold is not None:
signal.setitimer(signal.ITIMER_REAL, 0, 0)
if old_wakeup_fd is not None:
signal.set_wakeup_fd(old_wakeup_fd)

def stop(self):
"""Stop the loop after the current event loop iteration is complete.
Expand Down
3 changes: 3 additions & 0 deletions tornado/platform/common.py
Expand Up @@ -69,6 +69,9 @@ def __init__(self):
def fileno(self):
return self.reader.fileno()

def write_fileno(self):
return self.writer.fileno()

def wake(self):
try:
self.writer.send(b("x"))
Expand Down
6 changes: 5 additions & 1 deletion tornado/platform/interface.py
Expand Up @@ -39,13 +39,17 @@ class Waker(object):
the ``IOLoop`` is closed, it closes its waker too.
"""
def fileno(self):
"""Returns a file descriptor for this waker.
"""Returns the read file descriptor for this waker.
Must be suitable for use with ``select()`` or equivalent on the
local platform.
"""
raise NotImplementedError()

def write_fileno(self):
"""Returns the write file descriptor for this waker."""
raise NotImplementedError()

def wake(self):
"""Triggers activity on the waker's file descriptor."""
raise NotImplementedError()
Expand Down
3 changes: 3 additions & 0 deletions tornado/platform/posix.py
Expand Up @@ -48,6 +48,9 @@ def __init__(self):
def fileno(self):
return self.reader.fileno()

def write_fileno(self):
return self.writer.fileno()

def wake(self):
try:
self.writer.write(b("x"))
Expand Down

0 comments on commit 18af6c7

Please sign in to comment.