[3.13] gh-123720: When closing an asyncio server, stop the handlers (GH-124689) (#154681)

Yhg1s <[email protected]> Tue, 04 Aug 2026 04:58:30 -0400 (EDT)
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/696f8612d8becc398fc55763a3b632583cdd894e
commit: 696f8612d8becc398fc55763a3b632583cdd894e
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: Yhg1s <[email protected]>
date: 2026-08-04T10:58:13+02:00
summary:

[3.13] gh-123720: When closing an asyncio server, stop the handlers (GH-124689) (#154681)

gh-123720: When closing an asyncio server, stop the handlers (GH-124689)
(cherry picked from commit 788c3291172b55efa7cf8b33a315e4b0fe63540c)

Co-authored-by: Guido van Rossum <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-03-11-10-25-32.gh-issue-123720.TauFRx.rst
M Lib/asyncio/base_events.py
M Lib/test/test_asyncio/test_server.py

diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index ec8e3b6f79f4a0..f6f4123dd39221 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -380,6 +380,7 @@ async def serve_forever(self):
         except exceptions.CancelledError:
             try:
                 self.close()
+                self.close_clients()
                 await self.wait_closed()
             finally:
                 raise
diff --git a/Lib/test/test_asyncio/test_server.py b/Lib/test/test_asyncio/test_server.py
index 60a40cc8349fed..b571330c2f9cec 100644
--- a/Lib/test/test_asyncio/test_server.py
+++ b/Lib/test/test_asyncio/test_server.py
@@ -266,6 +266,38 @@ async def serve(rd, wr):
         await asyncio.sleep(0)
         self.assertTrue(task.done())
 
+    async def test_close_with_hanging_client(self):
+        # Synchronize server cancellation only after the socket connection is
+        # accepted and this event is set
+        conn_event = asyncio.Event()
+        class Proto(asyncio.Protocol):
+            def connection_made(self, transport):
+                conn_event.set()
+
+        loop = asyncio.get_running_loop()
+        srv = await loop.create_server(Proto, socket_helper.HOSTv4, 0)
+
+        # Start the server
+        serve_forever_task = asyncio.create_task(srv.serve_forever())
+        await asyncio.sleep(0)
+
+        # Create a connection to server
+        addr = srv.sockets[0].getsockname()
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.connect(addr)
+        self.addCleanup(sock.close)
+
+        # Send a CancelledError into the server to emulate a Ctrl+C
+        # KeyboardInterrupt whilst the server is handling a hanging client
+        await conn_event.wait()
+        serve_forever_task.cancel()
+
+        # Ensure the client is closed within a timeout
+        async with asyncio.timeout(0.5):
+            await srv.wait_closed()
+
+        self.assertFalse(srv.is_serving())
+
 
 # Test the various corner cases of Unix server socket removal
 class UnixServerCleanupTests(unittest.IsolatedAsyncioTestCase):
diff --git a/Misc/NEWS.d/next/Library/2026-03-11-10-25-32.gh-issue-123720.TauFRx.rst b/Misc/NEWS.d/next/Library/2026-03-11-10-25-32.gh-issue-123720.TauFRx.rst
new file mode 100644
index 00000000000000..04e6a377dd816c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-03-11-10-25-32.gh-issue-123720.TauFRx.rst
@@ -0,0 +1,5 @@
+asyncio: Fix :func:`asyncio.Server.serve_forever` shutdown regression. Since
+3.12, cancelling ``serve_forever()`` could hang waiting for a handler blocked
+on a read from a client that never closed (effectively requiring two
+interrupts to stop); the shutdown sequence now ensures client streams are
+closed so ``serve_forever()`` exits promptly and handlers observe EOF.

_______________________________________________
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]