Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 25 additions & 11 deletions Lib/asyncio/proactor_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -841,17 +841,31 @@ def loop(f=None):
if self._debug:
logger.debug("%r got a new connection from %r: %r",
server, addr, conn)
protocol = protocol_factory()
if sslcontext is not None:
self._make_ssl_transport(
conn, protocol, sslcontext, server_side=True,
extra={'peername': addr}, server=server,
ssl_handshake_timeout=ssl_handshake_timeout,
ssl_shutdown_timeout=ssl_shutdown_timeout)
else:
self._make_socket_transport(
conn, protocol,
extra={'peername': addr}, server=server)
protocol = None
try:
protocol = protocol_factory()
if sslcontext is not None:
self._make_ssl_transport(
conn, protocol, sslcontext, server_side=True,
extra={'peername': addr}, server=server,
ssl_handshake_timeout=ssl_handshake_timeout,
ssl_shutdown_timeout=ssl_shutdown_timeout)
else:
self._make_socket_transport(
conn, protocol,
extra={'peername': addr}, server=server)
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
conn.close()
context = {
'message': 'Error on transport creation '
'for incoming connection',
'exception': exc,
}
if protocol is not None:
context['protocol'] = protocol
self.call_exception_handler(context)
if self.is_closed():
return
f = self._proactor.accept(sock)
Expand Down
44 changes: 44 additions & 0 deletions Lib/test/test_asyncio/test_proactor_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,50 @@ def test_create_server(self, m_log):
self.assertTrue(self.sock.close.called)
self.assertTrue(m_log.error.called)

def test_create_server_transport_creation_error(self):
# gh-156365: a protocol_factory() failure closes the accepted socket
# and keeps the server accepting; the listening socket stays open.
pf = mock.Mock(side_effect=RuntimeError)
call_soon = self.loop.call_soon = mock.Mock()
self.loop.call_exception_handler = mock.Mock()

self.loop._start_serving(pf, self.sock)
loop = call_soon.call_args[0][0]
loop()
self.proactor.accept.reset_mock()

conn = mock.Mock()
fut = mock.Mock()
fut.result.return_value = (conn, ('127.0.0.1', 1234))
loop(fut)

self.assertTrue(conn.close.called)
self.assertFalse(self.sock.close.called)
self.assertTrue(self.proactor.accept.called)
self.loop.call_exception_handler.assert_called_once()

def test_create_server_transport_oserror_keeps_listening(self):
# gh-156365: an OSError from transport creation must not close the
# listening socket (mistaken for an accept failure).
pf = mock.Mock()
call_soon = self.loop.call_soon = mock.Mock()
self.loop.call_exception_handler = mock.Mock()
self.loop._make_socket_transport = mock.Mock(side_effect=OSError)

self.loop._start_serving(pf, self.sock)
loop = call_soon.call_args[0][0]
loop()
self.proactor.accept.reset_mock()

conn = mock.Mock()
fut = mock.Mock()
fut.result.return_value = (conn, ('127.0.0.1', 1234))
loop(fut)

self.assertTrue(conn.close.called)
self.assertFalse(self.sock.close.called)
self.assertTrue(self.proactor.accept.called)

def test_create_server_cancel(self):
pf = mock.Mock()
call_soon = self.loop.call_soon = mock.Mock()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix a socket leak in :mod:`asyncio` when ``protocol_factory()`` or transport
creation fails for a connection accepted by a proactor-based server (the
default event loop on Windows). The accepted socket is now closed and the
error no longer stops the server from accepting new connections.
Loading