def stop(self):
self.reactor.crash()
+ def _run_callback(self, callback, *args, **kwargs):
+ try:
+ callback(*args, **kwargs)
+ except Exception:
+ self.handle_callback_exception(callback)
+
def add_timeout(self, deadline, callback):
if isinstance(deadline, (int, long, float)):
delay = max(deadline - self.time(), 0)
delay = deadline.total_seconds()
else:
raise TypeError("Unsupported deadline %r")
- return self.reactor.callLater(delay, wrap(callback))
+ return self.reactor.callLater(delay, self._run_callback, wrap(callback))
def remove_timeout(self, timeout):
timeout.cancel()
def add_callback(self, callback, *args, **kwargs):
- self.reactor.callFromThread(functools.partial(wrap(callback),
- *args, **kwargs))
+ self.reactor.callFromThread(self._run_callback,
+ wrap(callback), *args, **kwargs)
def add_callback_from_signal(self, callback, *args, **kwargs):
self.add_callback(callback, *args, **kwargs)
import contextlib
import datetime
import functools
+import sys
import threading
import time
from tornado.ioloop import IOLoop
-from tornado.stack_context import ExceptionStackContext, StackContext, wrap
+from tornado.stack_context import ExceptionStackContext, StackContext, wrap, NullContext
from tornado.testing import AsyncTestCase, bind_unused_port
from tornado.test.util import unittest
self.assertEqual("IOLoop is closing", str(e))
break
+ def test_handle_callback_exception(self):
+ # IOLoop.handle_callback_exception can be overridden to catch
+ # exceptions in callbacks.
+ def handle_callback_exception(callback):
+ self.assertIs(sys.exc_info()[0], ZeroDivisionError)
+ self.stop()
+ self.io_loop.handle_callback_exception = handle_callback_exception
+ with NullContext():
+ # remove the test StackContext that would see this uncaught
+ # exception as a test failure.
+ self.io_loop.add_callback(lambda: 1 / 0)
+ self.wait()
+
class TestIOLoopAddCallback(AsyncTestCase):
def setUp(self):