From 3e2ad8ec61a322370a6fbdfb2209cf74546f5e08 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 25 Apr 2017 10:57:18 +0900 Subject: [PATCH] bpo-29617: Remove Python 3.3 support from asyncio (GH-232) --- Lib/asyncio/base_events.py | 17 ++--- Lib/asyncio/base_subprocess.py | 15 ++-- Lib/asyncio/compat.py | 12 ---- Lib/asyncio/events.py | 9 +-- Lib/asyncio/futures.py | 122 +++++---------------------------- Lib/asyncio/proactor_events.py | 15 ++-- Lib/asyncio/selector_events.py | 15 ++-- Lib/asyncio/sslproto.py | 15 ++-- Lib/asyncio/tasks.py | 24 +++---- Lib/asyncio/test_utils.py | 11 --- Lib/asyncio/transports.py | 4 +- Lib/asyncio/unix_events.py | 29 +++----- 12 files changed, 65 insertions(+), 223 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 6624ac1d88..3ff511be44 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -29,7 +29,6 @@ import sys import warnings import weakref -from . import compat from . import coroutines from . import events from . import futures @@ -499,16 +498,12 @@ class BaseEventLoop(events.AbstractEventLoop): """Returns True if the event loop was 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): - if not self.is_closed(): - warnings.warn("unclosed event loop %r" % self, ResourceWarning, - source=self) - if not self.is_running(): - self.close() + def __del__(self): + if not self.is_closed(): + warnings.warn("unclosed event loop %r" % self, ResourceWarning, + source=self) + if not self.is_running(): + self.close() def is_running(self): """Returns True if the event loop is running.""" diff --git a/Lib/asyncio/base_subprocess.py b/Lib/asyncio/base_subprocess.py index a00d9d5732..cac8d962c0 100644 --- a/Lib/asyncio/base_subprocess.py +++ b/Lib/asyncio/base_subprocess.py @@ -2,7 +2,6 @@ import collections import subprocess import warnings -from . import compat from . import protocols from . import transports from .coroutines import coroutine @@ -121,15 +120,11 @@ class BaseSubprocessTransport(transports.SubprocessTransport): # 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): - if not self._closed: - warnings.warn("unclosed transport %r" % self, ResourceWarning, - source=self) - self.close() + def __del__(self): + if not self._closed: + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) + self.close() def get_pid(self): return self._pid diff --git a/Lib/asyncio/compat.py b/Lib/asyncio/compat.py index 4790bb4a35..520ec6870c 100644 --- a/Lib/asyncio/compat.py +++ b/Lib/asyncio/compat.py @@ -2,17 +2,5 @@ import sys -PY34 = sys.version_info >= (3, 4) PY35 = sys.version_info >= (3, 5) 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) diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index e85634e588..6af91374ec 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -19,20 +19,15 @@ import sys import threading import traceback -from asyncio import compat - def _get_function_source(func): - if compat.PY34: - func = inspect.unwrap(func) - elif hasattr(func, '__wrapped__'): - func = func.__wrapped__ + func = inspect.unwrap(func) if inspect.isfunction(func): code = func.__code__ return (code.co_filename, code.co_firstlineno) if isinstance(func, functools.partial): 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 None diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py index d11d289307..39721eaf00 100644 --- a/Lib/asyncio/futures.py +++ b/Lib/asyncio/futures.py @@ -27,86 +27,6 @@ _FINISHED = base_futures._FINISHED 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: """This class is *almost* compatible with concurrent.futures.Future. @@ -164,25 +84,21 @@ class Future: def __repr__(self): 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): - if not self._log_traceback: - # set_exception() was not called, or result() or exception() - # has consumed the exception - return - exc = self._exception - context = { - 'message': ('%s exception was never retrieved' - % self.__class__.__name__), - 'exception': exc, - 'future': self, - } - if self._source_traceback: - context['source_traceback'] = self._source_traceback - self._loop.call_exception_handler(context) + def __del__(self): + if not self._log_traceback: + # set_exception() was not called, or result() or exception() + # has consumed the exception + return + exc = self._exception + context = { + 'message': ('%s exception was never retrieved' + % self.__class__.__name__), + 'exception': exc, + 'future': self, + } + if self._source_traceback: + context['source_traceback'] = self._source_traceback + self._loop.call_exception_handler(context) def cancel(self): """Cancel the future and schedule callbacks. @@ -317,13 +233,7 @@ class Future: self._exception = exception self._state = _FINISHED self._schedule_callbacks() - if compat.PY34: - 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) + self._log_traceback = True def __iter__(self): if not self.done(): diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index ff12877fae..c85d4dafdd 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -10,7 +10,6 @@ import socket import warnings from . import base_events -from . import compat from . import constants from . import futures from . import sslproto @@ -86,15 +85,11 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin, self._read_fut.cancel() 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): - if self._sock is not None: - warnings.warn("unclosed transport %r" % self, ResourceWarning, - source=self) - self.close() + def __del__(self): + if self._sock is not None: + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) + self.close() def _fatal_error(self, exc, message='Fatal error on pipe transport'): if isinstance(exc, base_events._FATAL_ERROR_IGNORE): diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 9dbe550b01..4b403560c3 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -18,7 +18,6 @@ except ImportError: # pragma: no cover ssl = None from . import base_events -from . import compat from . import constants from . import events from . import futures @@ -621,15 +620,11 @@ class _SelectorTransport(transports._FlowControlMixin, self._loop._remove_writer(self._sock_fd) 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): - if self._sock is not None: - warnings.warn("unclosed transport %r" % self, ResourceWarning, - source=self) - self._sock.close() + def __del__(self): + if self._sock is not None: + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) + self._sock.close() def _fatal_error(self, exc, message='Fatal error on transport'): # Should be called from exception handler only. diff --git a/Lib/asyncio/sslproto.py b/Lib/asyncio/sslproto.py index ab7ff0bf93..61d31a3b29 100644 --- a/Lib/asyncio/sslproto.py +++ b/Lib/asyncio/sslproto.py @@ -6,7 +6,6 @@ except ImportError: # pragma: no cover ssl = None from . import base_events -from . import compat from . import protocols from . import transports from .log import logger @@ -325,15 +324,11 @@ class _SSLProtocolTransport(transports._FlowControlMixin, self._closed = True 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): - if not self._closed: - warnings.warn("unclosed transport %r" % self, ResourceWarning, - source=self) - self.close() + def __del__(self): + if not self._closed: + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) + self.close() def pause_reading(self): """Pause the receiving end. diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index f91e70aecb..4fbcf3ed2e 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -76,20 +76,16 @@ class Task(futures.Future): self._loop.call_soon(self._step) 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): - if self._state == futures._PENDING and self._log_destroy_pending: - context = { - 'task': self, - 'message': 'Task was destroyed but it is pending!', - } - if self._source_traceback: - context['source_traceback'] = self._source_traceback - self._loop.call_exception_handler(context) - futures.Future.__del__(self) + def __del__(self): + if self._state == futures._PENDING and self._log_destroy_pending: + context = { + 'task': self, + 'message': 'Task was destroyed but it is pending!', + } + if self._source_traceback: + context['source_traceback'] = self._source_traceback + self._loop.call_exception_handler(context) + futures.Future.__del__(self) def _repr_info(self): return base_tasks._task_repr_info(self) diff --git a/Lib/asyncio/test_utils.py b/Lib/asyncio/test_utils.py index b12d5db2a9..d273b08e8f 100644 --- a/Lib/asyncio/test_utils.py +++ b/Lib/asyncio/test_utils.py @@ -26,7 +26,6 @@ except ImportError: # pragma: no cover ssl = None from . import base_events -from . import compat from . import events from . import futures from . import selectors @@ -465,16 +464,6 @@ class TestCase(unittest.TestCase): # in an except block of a generator 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 def disable_logger(): diff --git a/Lib/asyncio/transports.py b/Lib/asyncio/transports.py index 0db0875715..a94079f433 100644 --- a/Lib/asyncio/transports.py +++ b/Lib/asyncio/transports.py @@ -1,7 +1,5 @@ """Abstract Transport class.""" -from asyncio import compat - __all__ = ['BaseTransport', 'ReadTransport', 'WriteTransport', 'Transport', 'DatagramTransport', 'SubprocessTransport', ] @@ -104,7 +102,7 @@ class WriteTransport(BaseTransport): The default implementation concatenates the arguments and calls write() on the result. """ - data = compat.flatten_list_bytes(list_of_data) + data = b''.join(list_of_data) self.write(data) def write_eof(self): diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py index 2806ea8dc9..bf682a1a98 100644 --- a/Lib/asyncio/unix_events.py +++ b/Lib/asyncio/unix_events.py @@ -13,7 +13,6 @@ import warnings from . import base_events from . import base_subprocess -from . import compat from . import constants from . import coroutines from . import events @@ -413,15 +412,11 @@ class _UnixReadPipeTransport(transports.ReadTransport): if not self._closing: 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): - if self._pipe is not None: - warnings.warn("unclosed transport %r" % self, ResourceWarning, - source=self) - self._pipe.close() + def __del__(self): + if self._pipe is not None: + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) + self._pipe.close() def _fatal_error(self, exc, message='Fatal error on pipe transport'): # should be called by exception handler only @@ -614,15 +609,11 @@ class _UnixWritePipeTransport(transports._FlowControlMixin, # write_eof is all what we needed to close the write pipe 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): - if self._pipe is not None: - warnings.warn("unclosed transport %r" % self, ResourceWarning, - source=self) - self._pipe.close() + def __del__(self): + if self._pipe is not None: + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) + self._pipe.close() def abort(self): self._close(None) -- 2.40.0