"""Schedules a callback on the IOLoop when the given future is finished.
"""
assert isinstance(future, IOLoop._FUTURE_TYPES)
+ callback = stack_context.wrap(callback)
future.add_done_callback(
lambda future: self.add_callback(
functools.partial(callback, future)))
from __future__ import absolute_import, division, with_statement
import datetime
import socket
+import threading
import time
from tornado.ioloop import IOLoop
from tornado.netutil import bind_sockets
+from tornado.stack_context import ExceptionStackContext
from tornado.testing import AsyncTestCase, LogTrapTestCase, get_unused_port
from tornado.test.util import unittest
future = self.wait()
self.assertTrue(future.done())
self.assertTrue(future.result() is None)
+
+ def test_add_future_stack_context(self):
+ ready = threading.Event()
+ def task():
+ # we must wait for the ioloop callback to be scheduled before
+ # the task completes to ensure that add_future adds the callback
+ # asynchronously (which is the scenario in which capturing
+ # the stack_context matters)
+ ready.wait(1)
+ assert ready.isSet(), "timed out"
+ raise Exception("worker")
+ def callback(future):
+ self.future = future
+ raise Exception("callback")
+ def handle_exception(typ, value, traceback):
+ self.exception = value
+ self.stop()
+ return True
+
+ # stack_context propagates to the ioloop callback, but the worker
+ # task just has its exceptions caught and saved in the Future.
+ with futures.ThreadPoolExecutor(1) as pool:
+ with ExceptionStackContext(handle_exception):
+ self.io_loop.add_future(pool.submit(task), callback)
+ ready.set()
+ self.wait()
+
+ self.assertEqual(self.exception.args[0], "callback")
+ self.assertEqual(self.future.exception().args[0], "worker")
TestIOLoopFutures = unittest.skipIf(
futures is None, "futures module not present")(TestIOLoopFutures)