diff -r 1313dca0e2a5 Lib/asyncio/futures.py --- a/Lib/asyncio/futures.py Thu Dec 19 13:47:40 2013 -0600 +++ b/Lib/asyncio/futures.py Thu Dec 19 21:21:42 2013 +0100 @@ -7,6 +7,7 @@ import concurrent.futures._base import logging +import sys import traceback from . import events @@ -17,6 +18,8 @@ from .log import logger _CANCELLED = 'CANCELLED' _FINISHED = 'FINISHED' +_PY34 = sys.version_info >= (3, 4) + # TODO: Do we really want to depend on concurrent.futures internals? Error = concurrent.futures._base.Error CancelledError = concurrent.futures.CancelledError @@ -128,7 +131,8 @@ class Future: _blocking = False # proper use of future (yield vs yield from) - _tb_logger = None + _traceback = None # Used for Python 3.4 and later + _tb_logger = None # Used for Python 3.3 only def __init__(self, *, loop=None): """Initialize the future. @@ -162,6 +166,12 @@ class Future: res += '<{}>'.format(self._state) return res + if _PY34: + def __del__(self): + if self._traceback is not None: + logger.error('Future/Task exception was never retrieved:\n%s', + ''.join(self._traceback)) + def cancel(self): """Cancel the future and schedule callbacks. @@ -214,9 +224,10 @@ class Future: raise CancelledError if self._state != _FINISHED: raise InvalidStateError('Result is not ready.') + self._traceback = None if self._tb_logger is not None: self._tb_logger.clear() - self._tb_logger = None + self._tb_logger = None if self._exception is not None: raise self._exception return self._result @@ -233,9 +244,10 @@ class Future: raise CancelledError if self._state != _FINISHED: raise InvalidStateError('Exception is not set.') + self._traceback = None if self._tb_logger is not None: self._tb_logger.clear() - self._tb_logger = None + self._tb_logger = None return self._exception def add_done_callback(self, fn): @@ -286,12 +298,17 @@ class Future: if self._state != _PENDING: raise InvalidStateError('{}: {!r}'.format(self._state, self)) self._exception = exception - self._tb_logger = _TracebackLogger(exception) self._state = _FINISHED self._schedule_callbacks() - # Arrange for the logger to be activated after all callbacks - # have had a chance to call result() or exception(). - self._loop.call_soon(self._tb_logger.activate) + if _PY34: + self._traceback = traceback.format_exception(exception.__class__, + exception, + exception.__traceback__) + else: + self._tb_logger = _TracebackLogger(exception) + # Arrange for the logger to be activated after all callbacks + # have had a chance to call result() or exception(). + self._loop.call_soon(self._tb_logger.activate) # Truly internal methods. diff -r 1313dca0e2a5 Lib/test/test_asyncio/test_tasks.py --- a/Lib/test/test_asyncio/test_tasks.py Thu Dec 19 13:47:40 2013 -0600 +++ b/Lib/test/test_asyncio/test_tasks.py Thu Dec 19 21:21:42 2013 +0100 @@ -1352,6 +1352,7 @@ class GatherTestsBase: c.set_result(3) d.cancel() e.set_exception(RuntimeError()) + e.exception() def test_return_exceptions(self): a, b, c, d = [futures.Future(loop=self.one_loop) for i in range(4)] @@ -1431,6 +1432,7 @@ class FutureGatherTests(GatherTestsBase, c.set_result(3) d.cancel() e.set_exception(RuntimeError()) + e.exception() def test_result_exception_one_cancellation(self): a, b, c, d, e, f = [futures.Future(loop=self.one_loop)