Commit 978a9afc authored by Victor Stinner's avatar Victor Stinner

Issue #23243, asyncio: Emit a ResourceWarning when an event loop or a transport

is not explicitly closed. Close also explicitly transports in test_sslproto.
parent 3c0cf059
...@@ -26,6 +26,7 @@ import threading ...@@ -26,6 +26,7 @@ import threading
import time import time
import traceback import traceback
import sys import sys
import warnings
from . import coroutines from . import coroutines
from . import events from . import events
...@@ -333,6 +334,16 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -333,6 +334,16 @@ 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 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): def is_running(self):
"""Returns True if the event loop is running.""" """Returns True if the event loop is running."""
return (self._owner is not None) return (self._owner is not None)
......
import collections import collections
import subprocess import subprocess
import sys
import warnings
from . import protocols from . import protocols
from . import transports from . import transports
...@@ -13,6 +15,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport): ...@@ -13,6 +15,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
stdin, stdout, stderr, bufsize, stdin, stdout, stderr, bufsize,
extra=None, **kwargs): extra=None, **kwargs):
super().__init__(extra) super().__init__(extra)
self._closed = False
self._protocol = protocol self._protocol = protocol
self._loop = loop self._loop = loop
self._pid = None self._pid = None
...@@ -40,7 +43,10 @@ class BaseSubprocessTransport(transports.SubprocessTransport): ...@@ -40,7 +43,10 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
program, self._pid) program, self._pid)
def __repr__(self): 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: if self._returncode is not None:
info.append('returncode=%s' % self._returncode) info.append('returncode=%s' % self._returncode)
...@@ -70,6 +76,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport): ...@@ -70,6 +76,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
raise NotImplementedError raise NotImplementedError
def close(self): def close(self):
self._closed = True
for proto in self._pipes.values(): for proto in self._pipes.values():
if proto is None: if proto is None:
continue continue
...@@ -77,6 +84,15 @@ class BaseSubprocessTransport(transports.SubprocessTransport): ...@@ -77,6 +84,15 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
if self._returncode is None: if self._returncode is None:
self.terminate() 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): def get_pid(self):
return self._pid return self._pid
...@@ -104,6 +120,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport): ...@@ -104,6 +120,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
Function called when an exception is raised during the creation Function called when an exception is raised during the creation
of a subprocess. of a subprocess.
""" """
self._closed = True
if self._loop.get_debug(): if self._loop.get_debug():
logger.warning('Exception during subprocess creation, ' logger.warning('Exception during subprocess creation, '
'kill the subprocess %r', 'kill the subprocess %r',
......
...@@ -195,9 +195,9 @@ class Future: ...@@ -195,9 +195,9 @@ class Future:
info = self._repr_info() info = self._repr_info()
return '<%s %s>' % (self.__class__.__name__, ' '.join(info)) return '<%s %s>' % (self.__class__.__name__, ' '.join(info))
# On Python 3.3 or older, objects with a destructor part of a reference # 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 # cycle are never destroyed. It's not more the case on Python 3.4 thanks
# the PEP 442. # to the PEP 442.
if _PY34: if _PY34:
def __del__(self): def __del__(self):
if not self._log_traceback: if not self._log_traceback:
......
...@@ -7,6 +7,8 @@ proactor is only implemented on Windows with IOCP. ...@@ -7,6 +7,8 @@ proactor is only implemented on Windows with IOCP.
__all__ = ['BaseProactorEventLoop'] __all__ = ['BaseProactorEventLoop']
import socket import socket
import sys
import warnings
from . import base_events from . import base_events
from . import constants from . import constants
...@@ -74,6 +76,15 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin, ...@@ -74,6 +76,15 @@ 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 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'): def _fatal_error(self, exc, message='Fatal error on pipe transport'):
if isinstance(exc, (BrokenPipeError, ConnectionResetError)): if isinstance(exc, (BrokenPipeError, ConnectionResetError)):
if self._loop.get_debug(): if self._loop.get_debug():
......
...@@ -10,6 +10,8 @@ import collections ...@@ -10,6 +10,8 @@ import collections
import errno import errno
import functools import functools
import socket import socket
import sys
import warnings
try: try:
import ssl import ssl
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
...@@ -499,6 +501,11 @@ class _SelectorTransport(transports._FlowControlMixin, ...@@ -499,6 +501,11 @@ class _SelectorTransport(transports._FlowControlMixin,
_buffer_factory = bytearray # Constructs initial value for self._buffer. _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): def __init__(self, loop, sock, protocol, extra=None, server=None):
super().__init__(extra, loop) super().__init__(extra, loop)
self._extra['socket'] = sock self._extra['socket'] = sock
...@@ -559,6 +566,15 @@ class _SelectorTransport(transports._FlowControlMixin, ...@@ -559,6 +566,15 @@ class _SelectorTransport(transports._FlowControlMixin,
self._conn_lost += 1 self._conn_lost += 1
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 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'): def _fatal_error(self, exc, message='Fatal error on transport'):
# Should be called from exception handler only. # Should be called from exception handler only.
if isinstance(exc, (BrokenPipeError, if isinstance(exc, (BrokenPipeError,
......
import collections import collections
import sys
import warnings
try: try:
import ssl import ssl
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
...@@ -295,6 +297,7 @@ class _SSLProtocolTransport(transports._FlowControlMixin, ...@@ -295,6 +297,7 @@ class _SSLProtocolTransport(transports._FlowControlMixin,
self._loop = loop self._loop = loop
self._ssl_protocol = ssl_protocol self._ssl_protocol = ssl_protocol
self._app_protocol = app_protocol self._app_protocol = app_protocol
self._closed = False
def get_extra_info(self, name, default=None): def get_extra_info(self, name, default=None):
"""Get optional transport information.""" """Get optional transport information."""
...@@ -308,8 +311,18 @@ class _SSLProtocolTransport(transports._FlowControlMixin, ...@@ -308,8 +311,18 @@ class _SSLProtocolTransport(transports._FlowControlMixin,
protocol's connection_lost() method will (eventually) called protocol's connection_lost() method will (eventually) called
with None as its argument. with None as its argument.
""" """
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 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): def pause_reading(self):
"""Pause the receiving end. """Pause the receiving end.
......
...@@ -8,6 +8,7 @@ import stat ...@@ -8,6 +8,7 @@ import stat
import subprocess import subprocess
import sys import sys
import threading import threading
import warnings
from . import base_events from . import base_events
...@@ -353,6 +354,15 @@ class _UnixReadPipeTransport(transports.ReadTransport): ...@@ -353,6 +354,15 @@ 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 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'): def _fatal_error(self, exc, message='Fatal error on pipe transport'):
# should be called by exception handler only # should be called by exception handler only
if (isinstance(exc, OSError) and exc.errno == errno.EIO): if (isinstance(exc, OSError) and exc.errno == errno.EIO):
...@@ -529,6 +539,15 @@ class _UnixWritePipeTransport(transports._FlowControlMixin, ...@@ -529,6 +539,15 @@ 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 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): def abort(self):
self._close(None) self._close(None)
......
...@@ -14,6 +14,7 @@ import os ...@@ -14,6 +14,7 @@ import os
import socket import socket
import subprocess import subprocess
import tempfile import tempfile
import warnings
__all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle'] __all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle']
...@@ -156,7 +157,10 @@ class PipeHandle: ...@@ -156,7 +157,10 @@ class PipeHandle:
CloseHandle(self._handle) CloseHandle(self._handle)
self._handle = None 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): def __enter__(self):
return self return self
......
...@@ -499,8 +499,12 @@ class BaseProactorEventLoopTests(test_utils.TestCase): ...@@ -499,8 +499,12 @@ class BaseProactorEventLoopTests(test_utils.TestCase):
self.proactor.accept.assert_called_with(self.sock) self.proactor.accept.assert_called_with(self.sock)
def test_socketpair(self): def test_socketpair(self):
class EventLoop(BaseProactorEventLoop):
# override the destructor to not log a ResourceWarning
def __del__(self):
pass
self.assertRaises( self.assertRaises(
NotImplementedError, BaseProactorEventLoop, self.proactor) NotImplementedError, EventLoop, self.proactor)
def test_make_socket_transport(self): def test_make_socket_transport(self):
tr = self.loop._make_socket_transport(self.sock, asyncio.Protocol()) tr = self.loop._make_socket_transport(self.sock, asyncio.Protocol())
......
...@@ -22,7 +22,9 @@ class SslProtoHandshakeTests(test_utils.TestCase): ...@@ -22,7 +22,9 @@ class SslProtoHandshakeTests(test_utils.TestCase):
def ssl_protocol(self, waiter=None): def ssl_protocol(self, waiter=None):
sslcontext = test_utils.dummy_ssl_context() sslcontext = test_utils.dummy_ssl_context()
app_proto = asyncio.Protocol() 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): def connection_made(self, ssl_proto, do_handshake=None):
transport = mock.Mock() transport = mock.Mock()
...@@ -56,9 +58,6 @@ class SslProtoHandshakeTests(test_utils.TestCase): ...@@ -56,9 +58,6 @@ class SslProtoHandshakeTests(test_utils.TestCase):
with test_utils.disable_logger(): with test_utils.disable_logger():
self.loop.run_until_complete(handshake_fut) self.loop.run_until_complete(handshake_fut)
# Close the transport
ssl_proto._app_transport.close()
def test_eof_received_waiter(self): def test_eof_received_waiter(self):
waiter = asyncio.Future(loop=self.loop) waiter = asyncio.Future(loop=self.loop)
ssl_proto = self.ssl_protocol(waiter) ssl_proto = self.ssl_protocol(waiter)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment