Skip to content

Commit

Permalink
Issue #23243, asyncio: Emit a ResourceWarning when an event loop or a…
Browse files Browse the repository at this point in the history
… transport

is not explicitly closed. Close also explicitly transports in test_sslproto.
  • Loading branch information
vstinner committed Jan 29, 2015
1 parent 3c0cf05 commit 978a9af
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 10 deletions.
11 changes: 11 additions & 0 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import time
import traceback
import sys
import warnings

from . import coroutines
from . import events
Expand Down Expand Up @@ -333,6 +334,16 @@ def is_closed(self):
"""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)
Expand Down
19 changes: 18 additions & 1 deletion Lib/asyncio/base_subprocess.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import collections
import subprocess
import sys
import warnings

from . import protocols
from . import transports
Expand All @@ -13,6 +15,7 @@ def __init__(self, loop, protocol, args, shell,
stdin, stdout, stderr, bufsize,
extra=None, **kwargs):
super().__init__(extra)
self._closed = False
self._protocol = protocol
self._loop = loop
self._pid = None
Expand Down Expand Up @@ -40,7 +43,10 @@ def __init__(self, loop, protocol, args, shell,
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)

Expand Down Expand Up @@ -70,13 +76,23 @@ def _make_read_subprocess_pipe_proto(self, fd):
raise NotImplementedError

def close(self):
self._closed = True
for proto in self._pipes.values():
if proto is None:
continue
proto.pipe.close()
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

Expand Down Expand Up @@ -104,6 +120,7 @@ def _kill_wait(self):
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',
Expand Down
6 changes: 3 additions & 3 deletions Lib/asyncio/futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ def __repr__(self):
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:
Expand Down
11 changes: 11 additions & 0 deletions Lib/asyncio/proactor_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
__all__ = ['BaseProactorEventLoop']

import socket
import sys
import warnings

from . import base_events
from . import constants
Expand Down Expand Up @@ -74,6 +76,15 @@ def close(self):
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():
Expand Down
16 changes: 16 additions & 0 deletions Lib/asyncio/selector_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import errno
import functools
import socket
import sys
import warnings
try:
import ssl
except ImportError: # pragma: no cover
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -559,6 +566,15 @@ def close(self):
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,
Expand Down
13 changes: 13 additions & 0 deletions Lib/asyncio/sslproto.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import collections
import sys
import warnings
try:
import ssl
except ImportError: # pragma: no cover
Expand Down Expand Up @@ -295,6 +297,7 @@ def __init__(self, loop, ssl_protocol, app_protocol):
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."""
Expand All @@ -308,8 +311,18 @@ def close(self):
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.
Expand Down
19 changes: 19 additions & 0 deletions Lib/asyncio/unix_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import subprocess
import sys
import threading
import warnings


from . import base_events
Expand Down Expand Up @@ -353,6 +354,15 @@ def close(self):
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):
Expand Down Expand Up @@ -529,6 +539,15 @@ def close(self):
# 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)

Expand Down
6 changes: 5 additions & 1 deletion Lib/asyncio/windows_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import socket
import subprocess
import tempfile
import warnings


__all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle']
Expand Down Expand Up @@ -156,7 +157,10 @@ def close(self, *, CloseHandle=_winapi.CloseHandle):
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
Expand Down
6 changes: 5 additions & 1 deletion Lib/test/test_asyncio/test_proactor_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,8 +499,12 @@ def test_sock_accept(self):
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())
Expand Down
7 changes: 3 additions & 4 deletions Lib/test/test_asyncio/test_sslproto.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ def setUp(self):
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()
Expand Down Expand Up @@ -56,9 +58,6 @@ def do_handshake(callback):
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)
Expand Down

0 comments on commit 978a9af

Please sign in to comment.