summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES8
-rw-r--r--concurrent/futures/_base.py47
-rw-r--r--concurrent/futures/_compat.py10
-rw-r--r--concurrent/futures/thread.py4
-rwxr-xr-xsetup.py2
5 files changed, 60 insertions, 11 deletions
diff --git a/CHANGES b/CHANGES
index 4418f07..c99eff1 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,11 @@
+2.2.0
+=====
+
+- Added the set_exception_info() and exception_info() methods to Future
+ to enable extraction of tracebacks on Python 2.x
+- Added support for Future.set_exception_info() to ThreadPoolExecutor
+
+
2.1.6
=====
diff --git a/concurrent/futures/_base.py b/concurrent/futures/_base.py
index 8ed69b7..6f0c0f3 100644
--- a/concurrent/futures/_base.py
+++ b/concurrent/futures/_base.py
@@ -6,6 +6,8 @@ import logging
import threading
import time
+from concurrent.futures._compat import reraise
+
try:
from collections import namedtuple
except ImportError:
@@ -290,6 +292,7 @@ class Future(object):
self._state = PENDING
self._result = None
self._exception = None
+ self._traceback = None
self._waiters = []
self._done_callbacks = []
@@ -353,7 +356,7 @@ class Future(object):
def __get_result(self):
if self._exception:
- raise self._exception
+ reraise(self._exception, self._traceback)
else:
return self._result
@@ -405,8 +408,9 @@ class Future(object):
else:
raise TimeoutError()
- def exception(self, timeout=None):
- """Return the exception raised by the call that the future represents.
+ def exception_info(self, timeout=None):
+ """Return a tuple of (exception, traceback) raised by the call that the
+ future represents.
Args:
timeout: The number of seconds to wait for the exception if the
@@ -422,22 +426,40 @@ class Future(object):
TimeoutError: If the future didn't finish executing before the given
timeout.
"""
-
with self._condition:
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
raise CancelledError()
elif self._state == FINISHED:
- return self._exception
+ return self._exception, self._traceback
self._condition.wait(timeout)
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
raise CancelledError()
elif self._state == FINISHED:
- return self._exception
+ return self._exception, self._traceback
else:
raise TimeoutError()
+ def exception(self, timeout=None):
+ """Return the exception raised by the call that the future represents.
+
+ Args:
+ timeout: The number of seconds to wait for the exception if the
+ future isn't done. If None, then there is no limit on the wait
+ time.
+
+ Returns:
+ The exception raised by the call that the future represents or None
+ if the call completed without raising.
+
+ Raises:
+ CancelledError: If the future was cancelled.
+ TimeoutError: If the future didn't finish executing before the given
+ timeout.
+ """
+ return self.exception_info(timeout)[0]
+
# The following methods should only be used by Executors and in tests.
def set_running_or_notify_cancel(self):
"""Mark the future as running or process any cancel notifications.
@@ -492,19 +514,28 @@ class Future(object):
self._condition.notify_all()
self._invoke_callbacks()
- def set_exception(self, exception):
- """Sets the result of the future as being the given exception.
+ def set_exception_info(self, exception, traceback):
+ """Sets the result of the future as being the given exception
+ and traceback.
Should only be used by Executor implementations and unit tests.
"""
with self._condition:
self._exception = exception
+ self._traceback = traceback
self._state = FINISHED
for waiter in self._waiters:
waiter.add_exception(self)
self._condition.notify_all()
self._invoke_callbacks()
+ def set_exception(self, exception):
+ """Sets the result of the future as being the given exception.
+
+ Should only be used by Executor implementations and unit tests.
+ """
+ self.set_exception_info(exception, None)
+
class Executor(object):
"""This is an abstract base class for concrete asynchronous executors."""
diff --git a/concurrent/futures/_compat.py b/concurrent/futures/_compat.py
index 1146232..e77cf0e 100644
--- a/concurrent/futures/_compat.py
+++ b/concurrent/futures/_compat.py
@@ -99,3 +99,13 @@ def namedtuple(typename, field_names):
result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
return result
+
+
+if _sys.version_info[0] < 3:
+ def reraise(exc, traceback):
+ locals_ = {'exc_type': type(exc), 'exc_value': exc, 'traceback': traceback}
+ exec('raise exc_type, exc_value, traceback', {}, locals_)
+else:
+ def reraise(exc, traceback):
+ # Tracebacks are embedded in exceptions in Python 3
+ raise exc
diff --git a/concurrent/futures/thread.py b/concurrent/futures/thread.py
index a45959d..930d167 100644
--- a/concurrent/futures/thread.py
+++ b/concurrent/futures/thread.py
@@ -60,8 +60,8 @@ class _WorkItem(object):
try:
result = self.fn(*self.args, **self.kwargs)
except BaseException:
- e = sys.exc_info()[1]
- self.future.set_exception(e)
+ e, tb = sys.exc_info()[1:]
+ self.future.set_exception_info(e, tb)
else:
self.future.set_result(result)
diff --git a/setup.py b/setup.py
index 79be06c..2961f2a 100755
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@ except ImportError:
from distutils.core import setup
setup(name='futures',
- version='2.1.6',
+ version='2.2.0',
description='Backport of the concurrent.futures package from Python 3.2',
author='Brian Quinlan',
author_email='brian@sweetapp.com',