This allows for better tracebacks when using Futures on Python 2.
Future = futures.Future
+class TracebackFuture(Future):
+ """Subclass of `Future` which can store a traceback with exceptions.
+
+ The traceback is automatically available in Python 3, but in the
+ Python 2 futures backport this information is discarded.
+ """
+ def __init__(self):
+ super(TracebackFuture, self).__init__()
+ self.__exc_info = None
+
+ def exc_info(self):
+ return self.__exc_info
+
+ def set_exc_info(self, exc_info):
+ """Traceback-aware replacement for `Future.set_exception`."""
+ self.__exc_info = exc_info
+ self.set_exception(exc_info[1])
+
+ def result(self):
+ if self.__exc_info is not None:
+ raise_exc_info(self.__exc_info)
+ else:
+ return super(TracebackFuture, self).result()
+
+
class DummyExecutor(object):
def submit(self, fn, *args, **kwargs):
- future = Future()
+ future = TracebackFuture()
try:
future.set_result(fn(*args, **kwargs))
- except Exception as e:
- future.set_exception(e)
+ except Exception:
+ future.set_exc_info(sys.exc_info())
return future
dummy_executor = DummyExecutor()
replacer = ArgReplacer(f, 'callback')
@functools.wraps(f)
def wrapper(*args, **kwargs):
- future = Future()
+ future = TracebackFuture()
callback, args, kwargs = replacer.replace(
lambda value=_NO_RESULT: future.set_result(value),
args, kwargs)
def handle_error(typ, value, tb):
- future.set_exception(value)
+ future.set_exc_info((typ, value, tb))
return True
exc_info = None
with ExceptionStackContext(handle_error):
"""
def copy(future):
assert future is a
- if a.exception() is not None:
+ if (isinstance(a, TracebackFuture) and isinstance(b, TracebackFuture)
+ and a.exc_info() is not None):
+ b.set_exc_info(a.exc_info())
+ elif a.exception() is not None:
b.set_exception(a.exception())
else:
b.set_result(a.result())
import sys
import types
-from tornado.concurrent import Future
+from tornado.concurrent import Future, TracebackFuture
from tornado.ioloop import IOLoop
from tornado.stack_context import ExceptionStackContext
@functools.wraps(func)
def wrapper(*args, **kwargs):
runner = None
- future = Future()
+ future = TracebackFuture()
if 'callback' in kwargs:
callback = kwargs.pop('callback')
try:
if runner is not None and runner.handle_exception(typ, value, tb):
return True
- except Exception as e:
- # can't just say "Exception as value" - exceptions are cleared
- # from local namespace after except clause finishes.
- value = e
- future.set_exception(value)
+ except Exception:
+ typ, value, tb = sys.exc_info()
+ future.set_exc_info((typ, value, tb))
return True
with ExceptionStackContext(handle_exception) as deactivate:
try:
result = func(*args, **kwargs)
except (Return, StopIteration) as e:
result = getattr(e, 'value', None)
- except Exception as e:
+ except Exception:
deactivate()
- future.set_exception(e)
+ future.set_exc_info(sys.exc_info())
return future
else:
if isinstance(result, types.GeneratorType):
import numbers
import os
import select
+import sys
import threading
import time
import traceback
-from tornado.concurrent import Future
+from tornado.concurrent import Future, TracebackFuture
from tornado.log import app_log, gen_log
from tornado import stack_context
from tornado.util import Configurable
def run():
try:
result = func()
- except Exception as e:
- future_cell[0] = Future()
- future_cell[0].set_exception(e)
+ except Exception:
+ future_cell[0] = TracebackFuture()
+ future_cell[0].set_exc_info(sys.exc_info())
else:
if isinstance(result, Future):
future_cell[0] = result
import logging
import re
import socket
+import sys
+import traceback
from tornado.concurrent import Future, return_future, ReturnValueIgnoredError
from tornado.escape import utf8, to_unicode
self.assertIs(result, None)
future.result()
+ @gen_test
+ def test_future_traceback(self):
+ @return_future
+ @gen.engine
+ def f(callback):
+ yield gen.Task(self.io_loop.add_callback)
+ try:
+ 1 / 0
+ except ZeroDivisionError:
+ self.expected_frame = traceback.extract_tb(
+ sys.exc_info()[2], limit=1)[0]
+ raise
+ try:
+ yield f()
+ self.fail("didn't get expected exception")
+ except ZeroDivisionError:
+ tb = traceback.extract_tb(sys.exc_info()[2])
+ self.assertIn(self.expected_frame, tb)
+
# The following series of classes demonstrate and test various styles
# of use, with and without generators and futures.
class CapServer(TCPServer):