]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Use signal.set_wakeup_fd in IOLoop to close a race with signal handlers.
authorBen Darnell <ben@bendarnell.com>
Mon, 17 Sep 2012 07:22:39 +0000 (00:22 -0700)
committerBen Darnell <ben@bendarnell.com>
Mon, 17 Sep 2012 07:22:39 +0000 (00:22 -0700)
tornado/ioloop.py
tornado/platform/common.py
tornado/platform/interface.py
tornado/platform/posix.py

index b941087c1d1b5fe08ffed0bf6dffba1a4c12a440..6621f920e0ce6f007d5dd6369bba027e9bf31f29 100644 (file)
@@ -269,6 +269,36 @@ class IOLoop(object):
             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
 
@@ -349,6 +379,8 @@ class IOLoop(object):
         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.
index 176ce2e5292a94cabf57b52c05f000f275c9714c..39f60bd7989bc244911ef5aaf133a69df5a8b12f 100644 (file)
@@ -69,6 +69,9 @@ class Waker(interface.Waker):
     def fileno(self):
         return self.reader.fileno()
 
+    def write_fileno(self):
+        return self.writer.fileno()
+
     def wake(self):
         try:
             self.writer.send(b("x"))
index 21e72cd9eb7dc9e38c2bebec1c3f6b5cc8c729a7..5006f30b8fc0a5bfdffea00277ed081e8349075d 100644 (file)
@@ -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()
index 8d674c0e234b42a393c2ef5231a38474d9628cf0..487f97a9a7fc400e83dfd1fb793c59db4a597892 100644 (file)
@@ -48,6 +48,9 @@ class Waker(interface.Waker):
     def fileno(self):
         return self.reader.fileno()
 
+    def write_fileno(self):
+        return self.writer.fileno()
+
     def wake(self):
         try:
             self.writer.write(b("x"))