"""
try:
ret = callback()
- if ret is not None and is_future(ret):
+ if ret is not None:
+ from tornado import gen
# 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())
+ try:
+ ret = gen.convert_yielded(ret)
+ except gen.BadYieldError:
+ # It's not unusual for add_callback to be used with
+ # methods returning a non-None and non-yieldable
+ # result, which should just be ignored.
+ pass
+ else:
+ self.add_future(ret, lambda f: f.result())
except Exception:
self.handle_callback_exception(callback)
with ExpectLog(app_log, "Exception in callback"):
self.wait()
+ @skipBefore35
+ def test_exception_logging_native_coro(self):
+ """The IOLoop examines exceptions from awaitables and logs them."""
+ namespace = exec_test(globals(), locals(), """
+ async def callback():
+ self.io_loop.add_callback(self.stop)
+ 1 / 0
+ """)
+ with NullContext():
+ self.io_loop.add_callback(namespace["callback"])
+ with ExpectLog(app_log, "Exception in callback"):
+ self.wait()
def test_spawn_callback(self):
# An added callback runs in the test's stack_context, so will be
# re-arised in wait().