Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Lib/asyncio/selector_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,9 @@ async def sock_accept(self, sock):
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()
Expand Down
43 changes: 43 additions & 0 deletions Lib/test/test_asyncio/test_sock_lowlevel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import socket
import asyncio
import select
import sys
import unittest

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

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()
Expand All @@ -280,6 +313,16 @@ def test_sock_client_connect_racing(self):
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix cancelling :meth:`asyncio.loop.sock_accept` dropping a pending connection.
Loading