https://github.com/python/cpython/commit/ed5531cd7a9918a08b4a4df2200e733fa3625905 commit: ed5531cd7a9918a08b4a4df2200e733fa3625905 branch: 3.14 author: Miss Islington (bot) <[email protected]> committer: asvetlov <[email protected]> date: 2026-10-29T10:37:53Z summary:
[3.14] gh-109564: asyncio: drop connections accepted by a closed server (GH-160851) (#160879) gh-109564: asyncio: drop connections accepted by a closed server (GH-160851) If Server.close() ran after a connection was accepted but before its transport was created, Server._attach() hit an assertion, the error was reported to the exception handler, and the half-built transport later raised TypeError from __del__. Close such connections quietly instead, in both the selector and proactor accept paths. (cherry picked from commit 5364bdce6b6f2b0e055057aef1002ef07b91a497) Co-authored-by: Andrew Svetlov <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]> files: A Misc/NEWS.d/next/Library/2026-10-28-20-36-44.gh-issue-109564.WqoyNE.rst M Lib/asyncio/proactor_events.py M Lib/asyncio/selector_events.py M Lib/test/test_asyncio/test_server.py diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 0366bd7ba65fb0..2e4b9f06566c24 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -874,6 +874,11 @@ def loop(f=None): logger.debug("%r got a new connection from %r: %r", server, addr, conn) protocol = protocol_factory() + if server is not None and not server.is_serving(): + # The server was closed after the connection was + # accepted. + conn.close() + return if sslcontext is not None: self._make_ssl_transport( conn, protocol, sslcontext, server_side=True, diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 9ecb9d0634f306..e2bf9a5e4ade19 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -223,6 +223,10 @@ async def _accept_connection2( transport = None try: protocol = protocol_factory() + if server is not None and not server.is_serving(): + # The server was closed after the connection was accepted. + conn.close() + return waiter = self.create_future() if sslcontext: transport = self._make_ssl_transport( diff --git a/Lib/test/test_asyncio/test_server.py b/Lib/test/test_asyncio/test_server.py index 581ea47d2dec97..09566f30a9e45a 100644 --- a/Lib/test/test_asyncio/test_server.py +++ b/Lib/test/test_asyncio/test_server.py @@ -5,6 +5,7 @@ import threading import unittest +from test import support from test.support import socket_helper from test.test_asyncio import utils as test_utils from test.test_asyncio import functional as func_tests @@ -187,6 +188,37 @@ async def serve(rd, wr): loop.call_soon(wr.close) await srv.wait_closed() + async def test_close_before_transport_created(self): + # gh-109564: Closing the server after a connection was accepted, + # but before its transport was created, must drop the connection + # quietly. + connected = False + + class Proto(asyncio.Protocol): + def connection_made(self, transport): + nonlocal connected + connected = True + + def factory(): + srv.close() + return Proto() + + errors = [] + loop = asyncio.get_running_loop() + loop.set_exception_handler(lambda loop, ctx: errors.append(ctx)) + srv = await loop.create_server(factory, socket_helper.HOSTv4, 0) + self.addCleanup(srv.close) + + addr = srv.sockets[0].getsockname() + (rd, wr) = await asyncio.open_connection(addr[0], addr[1]) + self.addCleanup(wr.close) + async with asyncio.timeout(support.SHORT_TIMEOUT): + self.assertEqual(await rd.read(), b'') + await srv.wait_closed() + + self.assertFalse(connected) + self.assertEqual(errors, []) + async def test_close_clients(self): async def serve(rd, wr): try: diff --git a/Misc/NEWS.d/next/Library/2026-10-28-20-36-44.gh-issue-109564.WqoyNE.rst b/Misc/NEWS.d/next/Library/2026-10-28-20-36-44.gh-issue-109564.WqoyNE.rst new file mode 100644 index 00000000000000..66821f5cdc6e91 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-28-20-36-44.gh-issue-109564.WqoyNE.rst @@ -0,0 +1,4 @@ +Fix a race in :mod:`asyncio` where closing a server after it accepted a +connection, but before the connection's transport was created, reported an +:exc:`AssertionError` to the exception handler. Such connections are now +closed quietly. _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
