From 2237a72af711c49b020922c0f33a3fe63f6f8ee8 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sat, 3 Oct 2015 20:42:48 -0400 Subject: [PATCH] Recognize other yieldables in IOLoop._run_callback. --- tornado/ioloop.py | 13 +++++++++++-- tornado/test/ioloop_test.py | 12 ++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 742a84879..c23cb33e4 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -598,12 +598,21 @@ class IOLoop(Configurable): """ 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) diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index 8c2edd2f9..71b4ef873 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -363,6 +363,18 @@ class TestIOLoop(AsyncTestCase): 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(). -- 2.47.2