From 6bf831552db69e7c67e476feb32bfee44141161e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?tonghuaroot=20=28=E7=AB=A5=E8=AF=9D=29?= Date: Tue, 25 Aug 2026 21:37:16 +0800 Subject: [PATCH] gh-156365: Fix socket leak and stalled accept in the asyncio proactor server --- Lib/asyncio/proactor_events.py | 36 ++++++++++----- Lib/test/test_asyncio/test_proactor_events.py | 44 +++++++++++++++++++ ...-08-25-21-35-49.gh-issue-156365.pRocT2.rst | 4 ++ 3 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-25-21-35-49.gh-issue-156365.pRocT2.rst diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index f18a7fe58558155..3d084b1928e2be5 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -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) diff --git a/Lib/test/test_asyncio/test_proactor_events.py b/Lib/test/test_asyncio/test_proactor_events.py index 2f229887ad62cc8..c8b1f19de898797 100644 --- a/Lib/test/test_asyncio/test_proactor_events.py +++ b/Lib/test/test_asyncio/test_proactor_events.py @@ -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() diff --git a/Misc/NEWS.d/next/Library/2026-08-25-21-35-49.gh-issue-156365.pRocT2.rst b/Misc/NEWS.d/next/Library/2026-08-25-21-35-49.gh-issue-156365.pRocT2.rst new file mode 100644 index 000000000000000..5d9ed530ae9db63 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-25-21-35-49.gh-issue-156365.pRocT2.rst @@ -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.