For use in subclasses.
"""
try:
- callback()
+ ret = callback()
+ if ret is not None and is_future(ret):
+ # Functions that return Futures typically swallow all
+ # exceptions and store them in the Future. If a Future
+ # makes it out to the IOLoop, ensure its exception (if any)
+ # gets logged too.
+ self.add_future(ret, lambda f: f.result())
except Exception:
self.handle_callback_exception(callback)
def stop(self):
self.asyncio_loop.stop()
- 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, float)):
delay = max(deadline - self.time(), 0)
def add_callback(self, callback, *args, **kwargs):
if self.closing:
raise RuntimeError("IOLoop is closing")
- if kwargs:
- self.asyncio_loop.call_soon_threadsafe(functools.partial(
- self._run_callback, stack_context.wrap(callback),
- *args, **kwargs))
- else:
- self.asyncio_loop.call_soon_threadsafe(
- self._run_callback, stack_context.wrap(callback), *args)
+ self.asyncio_loop.call_soon_threadsafe(
+ self._run_callback,
+ functools.partial(stack_context.wrap(callback), *args, **kwargs))
add_callback_from_signal = add_callback
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)
timeout.cancel()
def add_callback(self, callback, *args, **kwargs):
- self.reactor.callFromThread(self._run_callback,
- wrap(callback), *args, **kwargs)
+ self.reactor.callFromThread(
+ self._run_callback,
+ functools.partial(wrap(callback), *args, **kwargs))
def add_callback_from_signal(self, callback, *args, **kwargs):
self.add_callback(callback, *args, **kwargs)
from tornado import gen
from tornado.ioloop import IOLoop, TimeoutError
+from tornado.log import app_log
from tornado.stack_context import ExceptionStackContext, StackContext, wrap, NullContext
-from tornado.testing import AsyncTestCase, bind_unused_port
+from tornado.testing import AsyncTestCase, bind_unused_port, ExpectLog
from tornado.test.util import unittest, skipIfNonUnix, skipOnTravis
try:
self.assertTrue(got_exception[0])
self.assertFalse(returned_from_start[0])
+ def test_exception_logging(self):
+ """Uncaught exceptions get logged by the IOLoop."""
+ # Use a NullContext to keep the exception from being caught by
+ # AsyncTestCase.
+ with NullContext():
+ self.io_loop.add_callback(lambda: 1/0)
+ self.io_loop.add_callback(self.stop)
+ with ExpectLog(app_log, "Exception in callback"):
+ self.wait()
+
+ def test_exception_logging_future(self):
+ """The IOLoop examines exceptions from Futures and logs them."""
+ with NullContext():
+ @gen.coroutine
+ def callback():
+ self.io_loop.add_callback(self.stop)
+ 1/0
+ self.io_loop.add_callback(callback)
+ with ExpectLog(app_log, "Exception in callback"):
+ self.wait()
+
# Deliberately not a subclass of AsyncTestCase so the IOLoop isn't
# automatically set as current.