Skip to content

Commit 5181170

Browse files
committed
gh-153761: Fix asyncio sock_accept() dropping a connection when cancelled
1 parent dd2faeb commit 5181170

3 files changed

Lines changed: 48 additions & 0 deletions

File tree

Lib/asyncio/selector_events.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,9 @@ async def sock_accept(self, sock):
718718
return await fut
719719

720720
def _sock_accept(self, fut, sock):
721+
# gh-153761: _sock_accept must not scheduled with already cancelled future
722+
if fut.done():
723+
return
721724
fd = sock.fileno()
722725
try:
723726
conn, address = sock.accept()

Lib/test/test_asyncio/test_sock_lowlevel.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import socket
22
import asyncio
3+
import select
34
import sys
45
import unittest
56

@@ -257,6 +258,38 @@ async def _basetest_sock_connect_racing(self, listener, sock):
257258

258259
self.skipTest(skip_reason)
259260

261+
async def _basetest_sock_accept_racing(self, listener, sock):
262+
# gh-153761: cancelling sock_accept() must not let a scheduled
263+
# _sock_accept run on the cancelled future.
264+
listener.setblocking(False)
265+
listener.bind(('127.0.0.1', 0))
266+
listener.listen(1)
267+
addr = listener.getsockname()
268+
269+
errors = []
270+
self.loop.set_exception_handler(lambda loop, ctx: errors.append(ctx))
271+
task = asyncio.create_task(self.loop.sock_accept(listener))
272+
await asyncio.sleep(0)
273+
274+
sock.connect(addr)
275+
select.select([listener], [], [], support.SHORT_TIMEOUT)
276+
277+
self.loop.call_soon(task.cancel)
278+
await asyncio.sleep(0)
279+
await asyncio.sleep(0)
280+
281+
with self.assertRaises(asyncio.CancelledError):
282+
await task
283+
self.assertEqual(errors, [])
284+
285+
# The pending connection must survive
286+
conn, conn_addr = await self.loop.sock_accept(listener)
287+
with conn:
288+
self.assertEqual(conn_addr, sock.getsockname())
289+
sock.setblocking(False)
290+
await self.loop.sock_sendall(conn, b'ping')
291+
self.assertEqual(await self.loop.sock_recv(sock, 4), b'ping')
292+
260293
def test_sock_client_racing(self):
261294
with test_utils.run_test_server() as httpd:
262295
sock = socket.socket()
@@ -280,6 +313,16 @@ def test_sock_client_connect_racing(self):
280313
self.loop.run_until_complete(asyncio.wait_for(
281314
self._basetest_sock_connect_racing(listener, sock), 10))
282315

316+
def test_sock_accept_racing(self):
317+
if sys.platform == 'win32':
318+
if isinstance(self.loop, asyncio.ProactorEventLoop):
319+
raise unittest.SkipTest('Not relevant to ProactorEventLoop')
320+
listener = socket.socket()
321+
sock = socket.socket()
322+
with listener, sock:
323+
self.loop.run_until_complete(asyncio.wait_for(
324+
self._basetest_sock_accept_racing(listener, sock), 10))
325+
283326
async def _basetest_huge_content(self, address):
284327
sock = socket.socket()
285328
sock.setblocking(False)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix cancelling :meth:`asyncio.BaseSelectorEventLoop._sock_accept` dropping a pending
2+
connection.

0 commit comments

Comments
 (0)