]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Remove synchronization for add_callback in the ioloop thread
authorClaudio Freire <klaussfreire@gmail.com>
Wed, 2 Sep 2015 17:32:46 +0000 (14:32 -0300)
committerClaudio Freire <klaussfreire@gmail.com>
Wed, 2 Sep 2015 17:32:46 +0000 (14:32 -0300)
It is not necessary, since it will never need to invoke
wake, and as noted in add_callback_from_signal it would pose
no race risk, and it is a considerable performance penalty.

tornado/ioloop.py

index 87d4168e49226ea77271c2b58adb3ae075887416..388d91b12bf18972d945b59eab4a747dfaf109dd 100644 (file)
@@ -909,20 +909,32 @@ class PollIOLoop(IOLoop):
         self._cancellations += 1
 
     def add_callback(self, callback, *args, **kwargs):
-        with self._callback_lock:
-            if self._closing:
-                raise RuntimeError("IOLoop is closing")
-            list_empty = not self._callbacks
+        if thread.get_ident() != self._thread_ident:
+            with self._callback_lock:
+                if self._closing:
+                    raise RuntimeError("IOLoop is closing")
+                list_empty = not self._callbacks
+                self._callbacks.append(functools.partial(
+                    stack_context.wrap(callback), *args, **kwargs))
+                if list_empty:
+                    # If we're in the IOLoop's thread, we know it's not currently
+                    # polling.  If we're not, and we added the first callback to an
+                    # empty list, we may need to wake it up (it may wake up on its
+                    # own, but an occasional extra wake is harmless).  Waking
+                    # up a polling IOLoop is relatively expensive, so we try to
+                    # avoid it when we can.
+                    self._waker.wake()
+        else:
+            # If we're on the IOLoop's thread, we don't need the lock,
+            # since we don't need to wake anyone, just add the callback.
+            # Blindly insert into self._callbacks.
+            # This is safe because the GIL makes list.append atomic.
+            # One subtlety is that if the thread is interrupting another
+            # thread holding the _callback_lock block in IOLoop.start, 
+            # we may modify either the old or new version of self._callbacks,
+            # but either way will work.
             self._callbacks.append(functools.partial(
                 stack_context.wrap(callback), *args, **kwargs))
-            if list_empty and thread.get_ident() != self._thread_ident:
-                # If we're in the IOLoop's thread, we know it's not currently
-                # polling.  If we're not, and we added the first callback to an
-                # empty list, we may need to wake it up (it may wake up on its
-                # own, but an occasional extra wake is harmless).  Waking
-                # up a polling IOLoop is relatively expensive, so we try to
-                # avoid it when we can.
-                self._waker.wake()
 
     def add_callback_from_signal(self, callback, *args, **kwargs):
         with stack_context.NullContext():