Close #19967: Thanks to the PEP 442, asyncio.Future can use a destructor in
Python 3.4 to log "uncatched" exceptions, instead of the dedicated _TracebackLogger class.
This commit is contained in:
parent
994bf4332f
commit
4c3c699e62
|
@ -7,6 +7,7 @@ __all__ = ['CancelledError', 'TimeoutError',
|
|||
|
||||
import concurrent.futures._base
|
||||
import logging
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from . import events
|
||||
|
@ -17,6 +18,8 @@ _PENDING = 'PENDING'
|
|||
_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,18 @@ 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.
|
||||
|
||||
|
|
|
@ -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, unittest.TestCase):
|
|||
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)
|
||||
|
|
Loading…
Reference in New Issue