mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
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
2 changed files with 27 additions and 7 deletions
|
@ -7,6 +7,7 @@ __all__ = ['CancelledError', 'TimeoutError',
|
||||||
|
|
||||||
import concurrent.futures._base
|
import concurrent.futures._base
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from . import events
|
from . import events
|
||||||
|
@ -17,6 +18,8 @@ _PENDING = 'PENDING'
|
||||||
_CANCELLED = 'CANCELLED'
|
_CANCELLED = 'CANCELLED'
|
||||||
_FINISHED = 'FINISHED'
|
_FINISHED = 'FINISHED'
|
||||||
|
|
||||||
|
_PY34 = sys.version_info >= (3, 4)
|
||||||
|
|
||||||
# TODO: Do we really want to depend on concurrent.futures internals?
|
# TODO: Do we really want to depend on concurrent.futures internals?
|
||||||
Error = concurrent.futures._base.Error
|
Error = concurrent.futures._base.Error
|
||||||
CancelledError = concurrent.futures.CancelledError
|
CancelledError = concurrent.futures.CancelledError
|
||||||
|
@ -128,7 +131,8 @@ class Future:
|
||||||
|
|
||||||
_blocking = False # proper use of future (yield vs yield from)
|
_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):
|
def __init__(self, *, loop=None):
|
||||||
"""Initialize the future.
|
"""Initialize the future.
|
||||||
|
@ -162,6 +166,12 @@ class Future:
|
||||||
res += '<{}>'.format(self._state)
|
res += '<{}>'.format(self._state)
|
||||||
return res
|
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):
|
def cancel(self):
|
||||||
"""Cancel the future and schedule callbacks.
|
"""Cancel the future and schedule callbacks.
|
||||||
|
|
||||||
|
@ -214,9 +224,10 @@ class Future:
|
||||||
raise CancelledError
|
raise CancelledError
|
||||||
if self._state != _FINISHED:
|
if self._state != _FINISHED:
|
||||||
raise InvalidStateError('Result is not ready.')
|
raise InvalidStateError('Result is not ready.')
|
||||||
|
self._traceback = None
|
||||||
if self._tb_logger is not None:
|
if self._tb_logger is not None:
|
||||||
self._tb_logger.clear()
|
self._tb_logger.clear()
|
||||||
self._tb_logger = None
|
self._tb_logger = None
|
||||||
if self._exception is not None:
|
if self._exception is not None:
|
||||||
raise self._exception
|
raise self._exception
|
||||||
return self._result
|
return self._result
|
||||||
|
@ -233,9 +244,10 @@ class Future:
|
||||||
raise CancelledError
|
raise CancelledError
|
||||||
if self._state != _FINISHED:
|
if self._state != _FINISHED:
|
||||||
raise InvalidStateError('Exception is not set.')
|
raise InvalidStateError('Exception is not set.')
|
||||||
|
self._traceback = None
|
||||||
if self._tb_logger is not None:
|
if self._tb_logger is not None:
|
||||||
self._tb_logger.clear()
|
self._tb_logger.clear()
|
||||||
self._tb_logger = None
|
self._tb_logger = None
|
||||||
return self._exception
|
return self._exception
|
||||||
|
|
||||||
def add_done_callback(self, fn):
|
def add_done_callback(self, fn):
|
||||||
|
@ -286,12 +298,18 @@ class Future:
|
||||||
if self._state != _PENDING:
|
if self._state != _PENDING:
|
||||||
raise InvalidStateError('{}: {!r}'.format(self._state, self))
|
raise InvalidStateError('{}: {!r}'.format(self._state, self))
|
||||||
self._exception = exception
|
self._exception = exception
|
||||||
self._tb_logger = _TracebackLogger(exception)
|
|
||||||
self._state = _FINISHED
|
self._state = _FINISHED
|
||||||
self._schedule_callbacks()
|
self._schedule_callbacks()
|
||||||
# Arrange for the logger to be activated after all callbacks
|
if _PY34:
|
||||||
# have had a chance to call result() or exception().
|
self._traceback = traceback.format_exception(
|
||||||
self._loop.call_soon(self._tb_logger.activate)
|
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.
|
# Truly internal methods.
|
||||||
|
|
||||||
|
|
|
@ -1352,6 +1352,7 @@ class GatherTestsBase:
|
||||||
c.set_result(3)
|
c.set_result(3)
|
||||||
d.cancel()
|
d.cancel()
|
||||||
e.set_exception(RuntimeError())
|
e.set_exception(RuntimeError())
|
||||||
|
e.exception()
|
||||||
|
|
||||||
def test_return_exceptions(self):
|
def test_return_exceptions(self):
|
||||||
a, b, c, d = [futures.Future(loop=self.one_loop) for i in range(4)]
|
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)
|
c.set_result(3)
|
||||||
d.cancel()
|
d.cancel()
|
||||||
e.set_exception(RuntimeError())
|
e.set_exception(RuntimeError())
|
||||||
|
e.exception()
|
||||||
|
|
||||||
def test_result_exception_one_cancellation(self):
|
def test_result_exception_one_cancellation(self):
|
||||||
a, b, c, d, e, f = [futures.Future(loop=self.one_loop)
|
a, b, c, d, e, f = [futures.Future(loop=self.one_loop)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue