]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
If a @return_future function raises immediately, don't run the callback.
authorBen Darnell <ben@bendarnell.com>
Sat, 23 Feb 2013 17:53:04 +0000 (12:53 -0500)
committerBen Darnell <ben@bendarnell.com>
Sat, 23 Feb 2013 17:53:04 +0000 (12:53 -0500)
tornado/concurrent.py
tornado/test/concurrent_test.py

index edef8c8c0eebf21adb2a1128652cb1a9845770ea..19f1c87be8e3d7bd44588e9ca307e8de1879a267 100644 (file)
@@ -150,8 +150,6 @@ def return_future(f):
         future = Future()
         callback, args, kwargs = replacer.replace(future.set_result,
                                                   args, kwargs)
-        if callback is not None:
-            future.add_done_callback(wrap(callback))
 
         def handle_error(typ, value, tb):
             future.set_exception(value)
@@ -172,6 +170,15 @@ def return_future(f):
             # go ahead and raise it to the caller directly without waiting
             # for them to inspect the Future.
             raise_exc_info(exc_info)
+
+        # If the caller passed in a callback, schedule it to be called
+        # when the future resolves.  It is important that this happens
+        # just before we return the future, or else we risk confusing
+        # stack contexts with multiple exceptions (one here with the
+        # immediate exception, and again when the future resolves and
+        # the callback triggers its exception by calling future.result()).
+        if callback is not None:
+            future.add_done_callback(wrap(callback))
         return future
     return wrapper
 
index e6fd1d7cec878df7300080404a80c44196c43177..f58e5c0629dbb1d5b76bbd21648a45f622e0b77b 100644 (file)
@@ -55,17 +55,14 @@ class ReturnFutureTest(AsyncTestCase):
         with self.assertRaises(ZeroDivisionError):
             # The caller sees the error just like a normal function.
             self.immediate_failure(callback=self.stop)
-        # The callback is also run, with a future that contains the error.
-        future = self.wait()
-        with self.assertRaises(ZeroDivisionError):
-            future.result()
+        # The callback is not run because the function failed synchronously.
+        self.io_loop.add_timeout(self.io_loop.time() + 0.05, self.stop)
+        result = self.wait()
+        self.assertIs(result, None)
 
     def test_return_value(self):
         with self.assertRaises(ReturnValueIgnoredError):
             self.return_value(callback=self.stop)
-        future = self.wait()
-        with self.assertRaises(ReturnValueIgnoredError):
-            future.result()
 
     def test_callback_kw(self):
         future = self.sync_future(callback=self.stop)