diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 46da66f7c4d422c..21abff88b5bf401 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 f41fa47b891d4b1..57ba2f13246c946 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -226,6 +226,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 67a46085be31ce0..5ebf7c04f2af225 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 000000000000000..66821f5cdc6e919 --- /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.