Skip to content

Commit

Permalink
io_loop: Don't spin CPU if callback returns {}
Browse files Browse the repository at this point in the history
Fixes an infinite loop by preventing _run_callback from directly calling
add_callback again when a callback returns {} or [].
  • Loading branch information
ajdavis authored and bwangelme committed Oct 31, 2016
1 parent 0817790 commit 45d2562
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 1 deletion.
6 changes: 5 additions & 1 deletion tornado/ioloop.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,10 +616,14 @@ def _run_callback(self, callback):
# 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.
Expand Down
20 changes: 20 additions & 0 deletions tornado/test/ioloop_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import sys
import threading
import time
import types

from tornado import gen
from tornado.ioloop import IOLoop, TimeoutError, PollIOLoop, PeriodicCallback
Expand Down Expand Up @@ -61,6 +62,25 @@ def sleep(self, t):


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
Expand Down

0 comments on commit 45d2562

Please sign in to comment.