]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
io_loop: Don't spin CPU if callback returns {} 1838/head
authorA. Jesse Jiryu Davis <jesse@mongodb.com>
Wed, 21 Sep 2016 19:47:39 +0000 (15:47 -0400)
committerA. Jesse Jiryu Davis <jesse@mongodb.com>
Thu, 22 Sep 2016 10:24:39 +0000 (06:24 -0400)
Fixes an infinite loop by preventing _run_callback from directly calling
add_callback again when a callback returns {} or [].

tornado/ioloop.py
tornado/test/ioloop_test.py

index cadb41161a7767156994821352741e9f0390be27..d61831766586cebe2c7a3e228caf73816050cae1 100644 (file)
@@ -616,10 +616,14 @@ class IOLoop(Configurable):
                     # result, which should just be ignored.
                     pass
                 else:
-                    self.add_future(ret, lambda f: f.result())
+                    self.add_future(ret, self._discard_future_result)
         except Exception:
             self.handle_callback_exception(callback)
 
+    def _discard_future_result(self, future):
+        """Avoid unhandled-exception warnings from spawned coroutines."""
+        future.result()
+
     def handle_callback_exception(self, callback):
         """This method is called whenever a callback run by the `IOLoop`
         throws an exception.
index 8570e73f011eb98f9fd6394216ed674a3b274a48..1bb8ce081782611db29479cc7f572a9e92e38e61 100644 (file)
@@ -9,6 +9,7 @@ import socket
 import sys
 import threading
 import time
+import types
 
 from tornado import gen
 from tornado.ioloop import IOLoop, TimeoutError, PollIOLoop, PeriodicCallback
@@ -61,6 +62,25 @@ class FakeTimeIOLoop(PollIOLoop):
 
 
 class TestIOLoop(AsyncTestCase):
+    def test_add_callback_return_sequence(self):
+        # A callback returning {} or [] shouldn't spin the CPU, see Issue #1803.
+        self.calls = 0
+
+        loop = self.io_loop
+        test = self
+        old_add_callback = loop.add_callback
+
+        def add_callback(self, callback, *args, **kwargs):
+            test.calls += 1
+            old_add_callback(callback, *args, **kwargs)
+
+        loop.add_callback = types.MethodType(add_callback, loop)
+        loop.add_callback(lambda: {})
+        loop.add_callback(lambda: [])
+        loop.add_timeout(datetime.timedelta(milliseconds=50), loop.stop)
+        loop.start()
+        self.assertLess(self.calls, 10)
+
     @skipOnTravis
     def test_add_callback_wakeup(self):
         # Make sure that add_callback from inside a running IOLoop