]> granicus.if.org Git - python/commitdiff
Issue #23243, asyncio: Emit a ResourceWarning when an event loop or a transport
authorVictor Stinner <victor.stinner@gmail.com>
Thu, 29 Jan 2015 16:50:58 +0000 (17:50 +0100)
committerVictor Stinner <victor.stinner@gmail.com>
Thu, 29 Jan 2015 16:50:58 +0000 (17:50 +0100)
is not explicitly closed. Close also explicitly transports in test_sslproto.

Lib/asyncio/base_events.py
Lib/asyncio/base_subprocess.py
Lib/asyncio/futures.py
Lib/asyncio/proactor_events.py
Lib/asyncio/selector_events.py
Lib/asyncio/sslproto.py
Lib/asyncio/unix_events.py
Lib/asyncio/windows_utils.py
Lib/test/test_asyncio/test_proactor_events.py
Lib/test/test_asyncio/test_sslproto.py

index e40d3ad5f2b4cd23fa71f4f9dbbc205456fbbb5d..7108f2516ad649500acc4f98123db17df143d4f6 100644 (file)
@@ -26,6 +26,7 @@ import threading
 import time
 import traceback
 import sys
+import warnings
 
 from . import coroutines
 from . import events
@@ -333,6 +334,16 @@ 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 sys.version_info >= (3, 4):
+        def __del__(self):
+            if not self.is_closed():
+                warnings.warn("unclosed event loop %r" % self, ResourceWarning)
+                if not self.is_running():
+                    self.close()
+
     def is_running(self):
         """Returns True if the event loop is running."""
         return (self._owner is not None)
index 81c6f1a71d364b46bedf74e331e8486a88aabb63..651a9a291ee174d4e2cac74d6996c08187373013 100644 (file)
@@ -1,5 +1,7 @@
 import collections
 import subprocess
+import sys
+import warnings
 
 from . import protocols
 from . import transports
@@ -13,6 +15,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
                  stdin, stdout, stderr, bufsize,
                  extra=None, **kwargs):
         super().__init__(extra)
+        self._closed = False
         self._protocol = protocol
         self._loop = loop
         self._pid = None
@@ -40,7 +43,10 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
                          program, self._pid)
 
     def __repr__(self):
-        info = [self.__class__.__name__, 'pid=%s' % self._pid]
+        info = [self.__class__.__name__]
+        if self._closed:
+            info.append('closed')
+        info.append('pid=%s' % self._pid)
         if self._returncode is not None:
             info.append('returncode=%s' % self._returncode)
 
@@ -70,6 +76,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
         raise NotImplementedError
 
     def close(self):
+        self._closed = True
         for proto in self._pipes.values():
             if proto is None:
                 continue
@@ -77,6 +84,15 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
         if self._returncode is None:
             self.terminate()
 
+    # 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 sys.version_info >= (3, 4):
+        def __del__(self):
+            if not self._closed:
+                warnings.warn("unclosed transport %r" % self, ResourceWarning)
+                self.close()
+
     def get_pid(self):
         return self._pid
 
@@ -104,6 +120,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
         Function called when an exception is raised during the creation
         of a subprocess.
         """
+        self._closed = True
         if self._loop.get_debug():
             logger.warning('Exception during subprocess creation, '
                            'kill the subprocess %r',
index 19212a94b9f9822d12af2552dedface73d6b7088..2c741fd422698c0a956b1fe890636f05a7acdc52 100644 (file)
@@ -195,9 +195,9 @@ class Future:
         info = self._repr_info()
         return '<%s %s>' % (self.__class__.__name__, ' '.join(info))
 
-    # On Python 3.3 or 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.
+    # 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 _PY34:
         def __del__(self):
             if not self._log_traceback:
index 0f533a5e59061c0e8cc74345f509e2b785a76d17..65de926be8e439d0b6d77a642bf1560a63739795 100644 (file)
@@ -7,6 +7,8 @@ proactor is only implemented on Windows with IOCP.
 __all__ = ['BaseProactorEventLoop']
 
 import socket
+import sys
+import warnings
 
 from . import base_events
 from . import constants
@@ -74,6 +76,15 @@ 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 sys.version_info >= (3, 4):
+        def __del__(self):
+            if self._sock is not None:
+                warnings.warn("unclosed transport %r" % self, ResourceWarning)
+                self.close()
+
     def _fatal_error(self, exc, message='Fatal error on pipe transport'):
         if isinstance(exc, (BrokenPipeError, ConnectionResetError)):
             if self._loop.get_debug():
index 914783266d1ed812f5b66584cea009817358d760..4bd6dc8d1cbf78b3d125708184e17bccc1727370 100644 (file)
@@ -10,6 +10,8 @@ import collections
 import errno
 import functools
 import socket
+import sys
+import warnings
 try:
     import ssl
 except ImportError:  # pragma: no cover
@@ -499,6 +501,11 @@ class _SelectorTransport(transports._FlowControlMixin,
 
     _buffer_factory = bytearray  # Constructs initial value for self._buffer.
 
+    # Attribute used in the destructor: it must be set even if the constructor
+    # is not called (see _SelectorSslTransport which may start by raising an
+    # exception)
+    _sock = None
+
     def __init__(self, loop, sock, protocol, extra=None, server=None):
         super().__init__(extra, loop)
         self._extra['socket'] = sock
@@ -559,6 +566,15 @@ class _SelectorTransport(transports._FlowControlMixin,
             self._conn_lost += 1
             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 sys.version_info >= (3, 4):
+        def __del__(self):
+            if self._sock is not None:
+                warnings.warn("unclosed transport %r" % self, ResourceWarning)
+                self._sock.close()
+
     def _fatal_error(self, exc, message='Fatal error on transport'):
         # Should be called from exception handler only.
         if isinstance(exc, (BrokenPipeError,
index fc809b9831de5abeceeb56ebb36083a37b16a5ce..235855e21e1cc9f0798b32298be4963b783ebce9 100644 (file)
@@ -1,4 +1,6 @@
 import collections
+import sys
+import warnings
 try:
     import ssl
 except ImportError:  # pragma: no cover
@@ -295,6 +297,7 @@ class _SSLProtocolTransport(transports._FlowControlMixin,
         self._loop = loop
         self._ssl_protocol = ssl_protocol
         self._app_protocol = app_protocol
+        self._closed = False
 
     def get_extra_info(self, name, default=None):
         """Get optional transport information."""
@@ -308,8 +311,18 @@ class _SSLProtocolTransport(transports._FlowControlMixin,
         protocol's connection_lost() method will (eventually) called
         with None as its argument.
         """
+        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 sys.version_info >= (3, 4):
+        def __del__(self):
+            if not self._closed:
+                warnings.warn("unclosed transport %r" % self, ResourceWarning)
+                self.close()
+
     def pause_reading(self):
         """Pause the receiving end.
 
index 7e1265a091f979f5b28ae05dd582fd30662d9282..b06f1b2330d76d5e87c7f6c361b5d81d1d6d38fe 100644 (file)
@@ -8,6 +8,7 @@ import stat
 import subprocess
 import sys
 import threading
+import warnings
 
 
 from . import base_events
@@ -353,6 +354,15 @@ 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 sys.version_info >= (3, 4):
+        def __del__(self):
+            if self._pipe is not None:
+                warnings.warn("unclosed transport %r" % self, ResourceWarning)
+                self._pipe.close()
+
     def _fatal_error(self, exc, message='Fatal error on pipe transport'):
         # should be called by exception handler only
         if (isinstance(exc, OSError) and exc.errno == errno.EIO):
@@ -529,6 +539,15 @@ 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 sys.version_info >= (3, 4):
+        def __del__(self):
+            if self._pipe is not None:
+                warnings.warn("unclosed transport %r" % self, ResourceWarning)
+                self._pipe.close()
+
     def abort(self):
         self._close(None)
 
index 5f8327eba63aa3827fc3398e26500121f1e76729..870cd13abe6cf2ff33425a7d93bbca78b1c75f06 100644 (file)
@@ -14,6 +14,7 @@ import os
 import socket
 import subprocess
 import tempfile
+import warnings
 
 
 __all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle']
@@ -156,7 +157,10 @@ class PipeHandle:
             CloseHandle(self._handle)
             self._handle = None
 
-    __del__ = close
+    def __del__(self):
+        if self._handle is not None:
+            warnings.warn("unclosed %r" % self, ResourceWarning)
+            self.close()
 
     def __enter__(self):
         return self
index 33a8a671ec1b03b1a81cfb95a612209a74e0840e..fcd9ab1e18f68943cfb83415b368636f2a518a41 100644 (file)
@@ -499,8 +499,12 @@ class BaseProactorEventLoopTests(test_utils.TestCase):
         self.proactor.accept.assert_called_with(self.sock)
 
     def test_socketpair(self):
+        class EventLoop(BaseProactorEventLoop):
+            # override the destructor to not log a ResourceWarning
+            def __del__(self):
+                pass
         self.assertRaises(
-            NotImplementedError, BaseProactorEventLoop, self.proactor)
+            NotImplementedError, EventLoop, self.proactor)
 
     def test_make_socket_transport(self):
         tr = self.loop._make_socket_transport(self.sock, asyncio.Protocol())
index 148e30dffeb4dab851caecc510cb018f2f663341..a72967ea0714d245ab5a2fa3514164331e84dbc5 100644 (file)
@@ -22,7 +22,9 @@ class SslProtoHandshakeTests(test_utils.TestCase):
     def ssl_protocol(self, waiter=None):
         sslcontext = test_utils.dummy_ssl_context()
         app_proto = asyncio.Protocol()
-        return sslproto.SSLProtocol(self.loop, app_proto, sslcontext, waiter)
+        proto = sslproto.SSLProtocol(self.loop, app_proto, sslcontext, waiter)
+        self.addCleanup(proto._app_transport.close)
+        return proto
 
     def connection_made(self, ssl_proto, do_handshake=None):
         transport = mock.Mock()
@@ -56,9 +58,6 @@ class SslProtoHandshakeTests(test_utils.TestCase):
         with test_utils.disable_logger():
             self.loop.run_until_complete(handshake_fut)
 
-        # Close the transport
-        ssl_proto._app_transport.close()
-
     def test_eof_received_waiter(self):
         waiter = asyncio.Future(loop=self.loop)
         ssl_proto = self.ssl_protocol(waiter)