[3.6] bpo-29406: asyncio SSL contexts leak sockets after calling close with certain servers (GH-409) (#2062)

* bpo-29406: asyncio SSL contexts leak sockets after calling close with certain servers (#409)

(cherry picked from commit a608d2d5a7)

* [3.6] bpo-29406: asyncio SSL contexts leak sockets after calling close with certain servers (GH-409)

* asyncio SSL contexts leak sockets after calling close with certain servers

* cleanup _shutdown_timeout_handle on _fatal_error.
(cherry picked from commit a608d2d5a7)
This commit is contained in:
Yury Selivanov 2017-06-10 10:00:45 -04:00 committed by GitHub
parent 911068e250
commit 6e14fd2a14
3 changed files with 61 additions and 1 deletions

View File

@ -7,6 +7,7 @@ except ImportError: # pragma: no cover
from . import base_events from . import base_events
from . import compat from . import compat
from . import futures
from . import protocols from . import protocols
from . import transports from . import transports
from .log import logger from .log import logger
@ -412,7 +413,7 @@ class SSLProtocol(protocols.Protocol):
def __init__(self, loop, app_protocol, sslcontext, waiter, def __init__(self, loop, app_protocol, sslcontext, waiter,
server_side=False, server_hostname=None, server_side=False, server_hostname=None,
call_connection_made=True): call_connection_made=True, shutdown_timeout=5.0):
if ssl is None: if ssl is None:
raise RuntimeError('stdlib ssl module not available') raise RuntimeError('stdlib ssl module not available')
@ -443,6 +444,8 @@ class SSLProtocol(protocols.Protocol):
self._session_established = False self._session_established = False
self._in_handshake = False self._in_handshake = False
self._in_shutdown = False self._in_shutdown = False
self._shutdown_timeout = shutdown_timeout
self._shutdown_timeout_handle = None
# transport, ex: SelectorSocketTransport # transport, ex: SelectorSocketTransport
self._transport = None self._transport = None
self._call_connection_made = call_connection_made self._call_connection_made = call_connection_made
@ -557,6 +560,15 @@ class SSLProtocol(protocols.Protocol):
self._in_shutdown = True self._in_shutdown = True
self._write_appdata(b'') self._write_appdata(b'')
if self._shutdown_timeout is not None:
self._shutdown_timeout_handle = self._loop.call_later(
self._shutdown_timeout, self._on_shutdown_timeout)
def _on_shutdown_timeout(self):
if self._transport is not None:
self._fatal_error(
futures.TimeoutError(), 'Can not complete shitdown operation')
def _write_appdata(self, data): def _write_appdata(self, data):
self._write_backlog.append((data, 0)) self._write_backlog.append((data, 0))
self._write_buffer_size += len(data) self._write_buffer_size += len(data)
@ -684,12 +696,22 @@ class SSLProtocol(protocols.Protocol):
}) })
if self._transport: if self._transport:
self._transport._force_close(exc) self._transport._force_close(exc)
self._transport = None
if self._shutdown_timeout_handle is not None:
self._shutdown_timeout_handle.cancel()
self._shutdown_timeout_handle = None
def _finalize(self): def _finalize(self):
self._sslpipe = None self._sslpipe = None
if self._transport is not None: if self._transport is not None:
self._transport.close() self._transport.close()
self._transport = None
if self._shutdown_timeout_handle is not None:
self._shutdown_timeout_handle.cancel()
self._shutdown_timeout_handle = None
def _abort(self): def _abort(self):
try: try:

View File

@ -96,6 +96,40 @@ class SslProtoHandshakeTests(test_utils.TestCase):
test_utils.run_briefly(self.loop) test_utils.run_briefly(self.loop)
self.assertIsInstance(waiter.exception(), ConnectionAbortedError) self.assertIsInstance(waiter.exception(), ConnectionAbortedError)
def test_close_abort(self):
# From issue #bpo-29406
# abort connection if server does not complete shutdown procedure
ssl_proto = self.ssl_protocol()
transport = self.connection_made(ssl_proto)
ssl_proto._on_handshake_complete(None)
ssl_proto._start_shutdown()
self.assertIsNotNone(ssl_proto._shutdown_timeout_handle)
exc_handler = mock.Mock()
self.loop.set_exception_handler(exc_handler)
ssl_proto._shutdown_timeout_handle._run()
exc_handler.assert_called_with(
self.loop, {'message': 'Can not complete shitdown operation',
'exception': mock.ANY,
'transport': transport,
'protocol': ssl_proto}
)
self.assertIsNone(ssl_proto._shutdown_timeout_handle)
def test_close(self):
# From issue #bpo-29406
# abort connection if server does not complete shutdown procedure
ssl_proto = self.ssl_protocol()
transport = self.connection_made(ssl_proto)
ssl_proto._on_handshake_complete(None)
ssl_proto._start_shutdown()
self.assertIsNotNone(ssl_proto._shutdown_timeout_handle)
ssl_proto._finalize()
self.assertIsNone(ssl_proto._transport)
self.assertIsNone(ssl_proto._shutdown_timeout_handle)
def test_close_during_handshake(self): def test_close_during_handshake(self):
# bpo-29743 Closing transport during handshake process leaks socket # bpo-29743 Closing transport during handshake process leaks socket
waiter = asyncio.Future(loop=self.loop) waiter = asyncio.Future(loop=self.loop)

View File

@ -49,6 +49,10 @@ Core and Builtins
Library Library
------- -------
- bpo-29406: asyncio SSL contexts leak sockets after calling close with
certain servers.
Patch by Nikolay Kim
- bpo-29870: Fix ssl sockets leaks when connection is aborted in asyncio/ssl - bpo-29870: Fix ssl sockets leaks when connection is aborted in asyncio/ssl
implementation. Patch by Michaël Sghaïer. implementation. Patch by Michaël Sghaïer.