bpo-29617: Remove Python 3.3 support from asyncio (GH-232)
This commit is contained in:
parent
f6448e5d65
commit
3e2ad8ec61
@ -29,7 +29,6 @@ import sys
|
|||||||
import warnings
|
import warnings
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
from . import compat
|
|
||||||
from . import coroutines
|
from . import coroutines
|
||||||
from . import events
|
from . import events
|
||||||
from . import futures
|
from . import futures
|
||||||
@ -499,10 +498,6 @@ class BaseEventLoop(events.AbstractEventLoop):
|
|||||||
"""Returns True if the event loop was closed."""
|
"""Returns True if the event loop was closed."""
|
||||||
return self._closed
|
return self._closed
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if not self.is_closed():
|
if not self.is_closed():
|
||||||
warnings.warn("unclosed event loop %r" % self, ResourceWarning,
|
warnings.warn("unclosed event loop %r" % self, ResourceWarning,
|
||||||
|
@ -2,7 +2,6 @@ import collections
|
|||||||
import subprocess
|
import subprocess
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from . import compat
|
|
||||||
from . import protocols
|
from . import protocols
|
||||||
from . import transports
|
from . import transports
|
||||||
from .coroutines import coroutine
|
from .coroutines import coroutine
|
||||||
@ -121,10 +120,6 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
|||||||
|
|
||||||
# Don't clear the _proc reference yet: _post_init() may still run
|
# Don't clear the _proc reference yet: _post_init() may still run
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if not self._closed:
|
if not self._closed:
|
||||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
||||||
|
@ -2,17 +2,5 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
PY34 = sys.version_info >= (3, 4)
|
|
||||||
PY35 = sys.version_info >= (3, 5)
|
PY35 = sys.version_info >= (3, 5)
|
||||||
PY352 = sys.version_info >= (3, 5, 2)
|
PY352 = sys.version_info >= (3, 5, 2)
|
||||||
|
|
||||||
|
|
||||||
def flatten_list_bytes(list_of_data):
|
|
||||||
"""Concatenate a sequence of bytes-like objects."""
|
|
||||||
if not PY34:
|
|
||||||
# On Python 3.3 and older, bytes.join() doesn't handle
|
|
||||||
# memoryview.
|
|
||||||
list_of_data = (
|
|
||||||
bytes(data) if isinstance(data, memoryview) else data
|
|
||||||
for data in list_of_data)
|
|
||||||
return b''.join(list_of_data)
|
|
||||||
|
@ -19,20 +19,15 @@ import sys
|
|||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from asyncio import compat
|
|
||||||
|
|
||||||
|
|
||||||
def _get_function_source(func):
|
def _get_function_source(func):
|
||||||
if compat.PY34:
|
|
||||||
func = inspect.unwrap(func)
|
func = inspect.unwrap(func)
|
||||||
elif hasattr(func, '__wrapped__'):
|
|
||||||
func = func.__wrapped__
|
|
||||||
if inspect.isfunction(func):
|
if inspect.isfunction(func):
|
||||||
code = func.__code__
|
code = func.__code__
|
||||||
return (code.co_filename, code.co_firstlineno)
|
return (code.co_filename, code.co_firstlineno)
|
||||||
if isinstance(func, functools.partial):
|
if isinstance(func, functools.partial):
|
||||||
return _get_function_source(func.func)
|
return _get_function_source(func.func)
|
||||||
if compat.PY34 and isinstance(func, functools.partialmethod):
|
if isinstance(func, functools.partialmethod):
|
||||||
return _get_function_source(func.func)
|
return _get_function_source(func.func)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -27,86 +27,6 @@ _FINISHED = base_futures._FINISHED
|
|||||||
STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging
|
STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging
|
||||||
|
|
||||||
|
|
||||||
class _TracebackLogger:
|
|
||||||
"""Helper to log a traceback upon destruction if not cleared.
|
|
||||||
|
|
||||||
This solves a nasty problem with Futures and Tasks that have an
|
|
||||||
exception set: if nobody asks for the exception, the exception is
|
|
||||||
never logged. This violates the Zen of Python: 'Errors should
|
|
||||||
never pass silently. Unless explicitly silenced.'
|
|
||||||
|
|
||||||
However, we don't want to log the exception as soon as
|
|
||||||
set_exception() is called: if the calling code is written
|
|
||||||
properly, it will get the exception and handle it properly. But
|
|
||||||
we *do* want to log it if result() or exception() was never called
|
|
||||||
-- otherwise developers waste a lot of time wondering why their
|
|
||||||
buggy code fails silently.
|
|
||||||
|
|
||||||
An earlier attempt added a __del__() method to the Future class
|
|
||||||
itself, but this backfired because the presence of __del__()
|
|
||||||
prevents garbage collection from breaking cycles. A way out of
|
|
||||||
this catch-22 is to avoid having a __del__() method on the Future
|
|
||||||
class itself, but instead to have a reference to a helper object
|
|
||||||
with a __del__() method that logs the traceback, where we ensure
|
|
||||||
that the helper object doesn't participate in cycles, and only the
|
|
||||||
Future has a reference to it.
|
|
||||||
|
|
||||||
The helper object is added when set_exception() is called. When
|
|
||||||
the Future is collected, and the helper is present, the helper
|
|
||||||
object is also collected, and its __del__() method will log the
|
|
||||||
traceback. When the Future's result() or exception() method is
|
|
||||||
called (and a helper object is present), it removes the helper
|
|
||||||
object, after calling its clear() method to prevent it from
|
|
||||||
logging.
|
|
||||||
|
|
||||||
One downside is that we do a fair amount of work to extract the
|
|
||||||
traceback from the exception, even when it is never logged. It
|
|
||||||
would seem cheaper to just store the exception object, but that
|
|
||||||
references the traceback, which references stack frames, which may
|
|
||||||
reference the Future, which references the _TracebackLogger, and
|
|
||||||
then the _TracebackLogger would be included in a cycle, which is
|
|
||||||
what we're trying to avoid! As an optimization, we don't
|
|
||||||
immediately format the exception; we only do the work when
|
|
||||||
activate() is called, which call is delayed until after all the
|
|
||||||
Future's callbacks have run. Since usually a Future has at least
|
|
||||||
one callback (typically set by 'yield from') and usually that
|
|
||||||
callback extracts the callback, thereby removing the need to
|
|
||||||
format the exception.
|
|
||||||
|
|
||||||
PS. I don't claim credit for this solution. I first heard of it
|
|
||||||
in a discussion about closing files when they are collected.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = ('loop', 'source_traceback', 'exc', 'tb')
|
|
||||||
|
|
||||||
def __init__(self, future, exc):
|
|
||||||
self.loop = future._loop
|
|
||||||
self.source_traceback = future._source_traceback
|
|
||||||
self.exc = exc
|
|
||||||
self.tb = None
|
|
||||||
|
|
||||||
def activate(self):
|
|
||||||
exc = self.exc
|
|
||||||
if exc is not None:
|
|
||||||
self.exc = None
|
|
||||||
self.tb = traceback.format_exception(exc.__class__, exc,
|
|
||||||
exc.__traceback__)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.exc = None
|
|
||||||
self.tb = None
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self.tb:
|
|
||||||
msg = 'Future/Task exception was never retrieved\n'
|
|
||||||
if self.source_traceback:
|
|
||||||
src = ''.join(traceback.format_list(self.source_traceback))
|
|
||||||
msg += 'Future/Task created at (most recent call last):\n'
|
|
||||||
msg += '%s\n' % src.rstrip()
|
|
||||||
msg += ''.join(self.tb).rstrip()
|
|
||||||
self.loop.call_exception_handler({'message': msg})
|
|
||||||
|
|
||||||
|
|
||||||
class Future:
|
class Future:
|
||||||
"""This class is *almost* compatible with concurrent.futures.Future.
|
"""This class is *almost* compatible with concurrent.futures.Future.
|
||||||
|
|
||||||
@ -164,10 +84,6 @@ class Future:
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s %s>' % (self.__class__.__name__, ' '.join(self._repr_info()))
|
return '<%s %s>' % (self.__class__.__name__, ' '.join(self._repr_info()))
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if not self._log_traceback:
|
if not self._log_traceback:
|
||||||
# set_exception() was not called, or result() or exception()
|
# set_exception() was not called, or result() or exception()
|
||||||
@ -317,13 +233,7 @@ class Future:
|
|||||||
self._exception = exception
|
self._exception = exception
|
||||||
self._state = _FINISHED
|
self._state = _FINISHED
|
||||||
self._schedule_callbacks()
|
self._schedule_callbacks()
|
||||||
if compat.PY34:
|
|
||||||
self._log_traceback = True
|
self._log_traceback = True
|
||||||
else:
|
|
||||||
self._tb_logger = _TracebackLogger(self, 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)
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
if not self.done():
|
if not self.done():
|
||||||
|
@ -10,7 +10,6 @@ import socket
|
|||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from . import base_events
|
from . import base_events
|
||||||
from . import compat
|
|
||||||
from . import constants
|
from . import constants
|
||||||
from . import futures
|
from . import futures
|
||||||
from . import sslproto
|
from . import sslproto
|
||||||
@ -86,10 +85,6 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
|||||||
self._read_fut.cancel()
|
self._read_fut.cancel()
|
||||||
self._read_fut = None
|
self._read_fut = None
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self._sock is not None:
|
if self._sock is not None:
|
||||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
||||||
|
@ -18,7 +18,6 @@ except ImportError: # pragma: no cover
|
|||||||
ssl = None
|
ssl = None
|
||||||
|
|
||||||
from . import base_events
|
from . import base_events
|
||||||
from . import compat
|
|
||||||
from . import constants
|
from . import constants
|
||||||
from . import events
|
from . import events
|
||||||
from . import futures
|
from . import futures
|
||||||
@ -621,10 +620,6 @@ class _SelectorTransport(transports._FlowControlMixin,
|
|||||||
self._loop._remove_writer(self._sock_fd)
|
self._loop._remove_writer(self._sock_fd)
|
||||||
self._loop.call_soon(self._call_connection_lost, None)
|
self._loop.call_soon(self._call_connection_lost, None)
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self._sock is not None:
|
if self._sock is not None:
|
||||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
||||||
|
@ -6,7 +6,6 @@ except ImportError: # pragma: no cover
|
|||||||
ssl = None
|
ssl = None
|
||||||
|
|
||||||
from . import base_events
|
from . import base_events
|
||||||
from . import compat
|
|
||||||
from . import protocols
|
from . import protocols
|
||||||
from . import transports
|
from . import transports
|
||||||
from .log import logger
|
from .log import logger
|
||||||
@ -325,10 +324,6 @@ class _SSLProtocolTransport(transports._FlowControlMixin,
|
|||||||
self._closed = True
|
self._closed = True
|
||||||
self._ssl_protocol._start_shutdown()
|
self._ssl_protocol._start_shutdown()
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if not self._closed:
|
if not self._closed:
|
||||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
||||||
|
@ -76,10 +76,6 @@ class Task(futures.Future):
|
|||||||
self._loop.call_soon(self._step)
|
self._loop.call_soon(self._step)
|
||||||
self.__class__._all_tasks.add(self)
|
self.__class__._all_tasks.add(self)
|
||||||
|
|
||||||
# On Python 3.3 or older, objects with a destructor that are part of a
|
|
||||||
# reference cycle are never destroyed. That's not the case any more on
|
|
||||||
# Python 3.4 thanks to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self._state == futures._PENDING and self._log_destroy_pending:
|
if self._state == futures._PENDING and self._log_destroy_pending:
|
||||||
context = {
|
context = {
|
||||||
|
@ -26,7 +26,6 @@ except ImportError: # pragma: no cover
|
|||||||
ssl = None
|
ssl = None
|
||||||
|
|
||||||
from . import base_events
|
from . import base_events
|
||||||
from . import compat
|
|
||||||
from . import events
|
from . import events
|
||||||
from . import futures
|
from . import futures
|
||||||
from . import selectors
|
from . import selectors
|
||||||
@ -465,16 +464,6 @@ class TestCase(unittest.TestCase):
|
|||||||
# in an except block of a generator
|
# in an except block of a generator
|
||||||
self.assertEqual(sys.exc_info(), (None, None, None))
|
self.assertEqual(sys.exc_info(), (None, None, None))
|
||||||
|
|
||||||
if not compat.PY34:
|
|
||||||
# Python 3.3 compatibility
|
|
||||||
def subTest(self, *args, **kwargs):
|
|
||||||
class EmptyCM:
|
|
||||||
def __enter__(self):
|
|
||||||
pass
|
|
||||||
def __exit__(self, *exc):
|
|
||||||
pass
|
|
||||||
return EmptyCM()
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def disable_logger():
|
def disable_logger():
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
"""Abstract Transport class."""
|
"""Abstract Transport class."""
|
||||||
|
|
||||||
from asyncio import compat
|
|
||||||
|
|
||||||
__all__ = ['BaseTransport', 'ReadTransport', 'WriteTransport',
|
__all__ = ['BaseTransport', 'ReadTransport', 'WriteTransport',
|
||||||
'Transport', 'DatagramTransport', 'SubprocessTransport',
|
'Transport', 'DatagramTransport', 'SubprocessTransport',
|
||||||
]
|
]
|
||||||
@ -104,7 +102,7 @@ class WriteTransport(BaseTransport):
|
|||||||
The default implementation concatenates the arguments and
|
The default implementation concatenates the arguments and
|
||||||
calls write() on the result.
|
calls write() on the result.
|
||||||
"""
|
"""
|
||||||
data = compat.flatten_list_bytes(list_of_data)
|
data = b''.join(list_of_data)
|
||||||
self.write(data)
|
self.write(data)
|
||||||
|
|
||||||
def write_eof(self):
|
def write_eof(self):
|
||||||
|
@ -13,7 +13,6 @@ import warnings
|
|||||||
|
|
||||||
from . import base_events
|
from . import base_events
|
||||||
from . import base_subprocess
|
from . import base_subprocess
|
||||||
from . import compat
|
|
||||||
from . import constants
|
from . import constants
|
||||||
from . import coroutines
|
from . import coroutines
|
||||||
from . import events
|
from . import events
|
||||||
@ -413,10 +412,6 @@ class _UnixReadPipeTransport(transports.ReadTransport):
|
|||||||
if not self._closing:
|
if not self._closing:
|
||||||
self._close(None)
|
self._close(None)
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self._pipe is not None:
|
if self._pipe is not None:
|
||||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
||||||
@ -614,10 +609,6 @@ class _UnixWritePipeTransport(transports._FlowControlMixin,
|
|||||||
# write_eof is all what we needed to close the write pipe
|
# write_eof is all what we needed to close the write pipe
|
||||||
self.write_eof()
|
self.write_eof()
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self._pipe is not None:
|
if self._pipe is not None:
|
||||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user