]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-153761: Fix asyncio sock_accept() dropping a connection when cancelled (#153762)
authorTimofei <128279579+deadlovelll@users.noreply.github.com>
Thu, 16 Jul 2026 06:05:25 +0000 (09:05 +0300)
committerGitHub <noreply@github.com>
Thu, 16 Jul 2026 06:05:25 +0000 (11:35 +0530)
Lib/asyncio/selector_events.py
Lib/test/test_asyncio/test_sock_lowlevel.py
Misc/NEWS.d/next/Library/2026-07-15-14-24-18.gh-issue-153761.8jqs67.rst [new file with mode: 0644]

index 97cb6b76ac2e89a1129ab41f7b7dfcc20ee553e0..19ffd8cc98b0e9db50f82741d45f2ec6215dbec3 100644 (file)
@@ -714,6 +714,9 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
         return await fut
 
     def _sock_accept(self, fut, sock):
+        # gh-153761: _sock_accept must not scheduled with already cancelled future
+        if fut.done():
+            return
         fd = sock.fileno()
         try:
             conn, address = sock.accept()
index 5d825440e41ca7cc5a09d0ca9d811e3dcaaa2f29..c68211968a80d832862e75c559e44dca8082ca64 100644 (file)
@@ -1,5 +1,6 @@
 import socket
 import asyncio
+import select
 import sys
 import unittest
 
@@ -257,6 +258,38 @@ class BaseSockTestsMixin:
 
         self.skipTest(skip_reason)
 
+    async def _basetest_sock_accept_racing(self, listener, sock):
+        # gh-153761: cancelling sock_accept() must not let a scheduled
+        # _sock_accept run on the cancelled future.
+        listener.setblocking(False)
+        listener.bind(('127.0.0.1', 0))
+        listener.listen(1)
+        addr = listener.getsockname()
+
+        errors = []
+        self.loop.set_exception_handler(lambda loop, ctx: errors.append(ctx))
+        task = asyncio.create_task(self.loop.sock_accept(listener))
+        await asyncio.sleep(0)
+
+        sock.connect(addr)
+        select.select([listener], [], [], support.SHORT_TIMEOUT)
+
+        self.loop.call_soon(task.cancel)
+        await asyncio.sleep(0)
+        await asyncio.sleep(0)
+
+        with self.assertRaises(asyncio.CancelledError):
+            await task
+        self.assertEqual(errors, [])
+
+        # The pending connection must survive
+        conn, conn_addr = await self.loop.sock_accept(listener)
+        with conn:
+            self.assertEqual(conn_addr, sock.getsockname())
+            sock.setblocking(False)
+            await self.loop.sock_sendall(conn, b'ping')
+            self.assertEqual(await self.loop.sock_recv(sock, 4), b'ping')
+
     def test_sock_client_racing(self):
         with test_utils.run_test_server() as httpd:
             sock = socket.socket()
@@ -280,6 +313,16 @@ class BaseSockTestsMixin:
             self.loop.run_until_complete(asyncio.wait_for(
                 self._basetest_sock_connect_racing(listener, sock), 10))
 
+    def test_sock_accept_racing(self):
+        if sys.platform == 'win32':
+            if isinstance(self.loop, asyncio.ProactorEventLoop):
+                raise unittest.SkipTest('Not relevant to ProactorEventLoop')
+        listener = socket.socket()
+        sock = socket.socket()
+        with listener, sock:
+            self.loop.run_until_complete(asyncio.wait_for(
+                self._basetest_sock_accept_racing(listener, sock), 10))
+
     async def _basetest_huge_content(self, address):
         sock = socket.socket()
         sock.setblocking(False)
diff --git a/Misc/NEWS.d/next/Library/2026-07-15-14-24-18.gh-issue-153761.8jqs67.rst b/Misc/NEWS.d/next/Library/2026-07-15-14-24-18.gh-issue-153761.8jqs67.rst
new file mode 100644 (file)
index 0000000..68e007e
--- /dev/null
@@ -0,0 +1 @@
+Fix cancelling :meth:`asyncio.loop.sock_accept` dropping a pending connection.