gh-127529: Correct asyncio's accept_connection behaviour for handling ConnectionAbortedError (#127532)

Co-authored-by: Kumar Aditya <kumaraditya@python.org>
This commit is contained in:
jb2170 2025-01-03 10:32:36 +00:00 committed by GitHub
parent bb2dfadb92
commit 830e10651b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 36 additions and 3 deletions

View file

@ -180,9 +180,13 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
logger.debug("%r got a new connection from %r: %r",
server, addr, conn)
conn.setblocking(False)
except (BlockingIOError, InterruptedError, ConnectionAbortedError):
# Early exit because the socket accept buffer is empty.
return None
except ConnectionAbortedError:
# Discard connections that were aborted before accept().
continue
except (BlockingIOError, InterruptedError):
# Early exit because of a signal or
# the socket accept buffer is empty.
return
except OSError as exc:
# There's nowhere to send the error, so just log it.
if exc.errno in (errno.EMFILE, errno.ENFILE,

View file

@ -364,6 +364,31 @@ class BaseSelectorEventLoopTests(test_utils.TestCase):
self.loop.run_until_complete(asyncio.sleep(0))
self.assertEqual(sock.accept.call_count, backlog)
def test_accept_connection_skip_connectionabortederror(self):
sock = mock.Mock()
def mock_sock_accept():
# mock accept(2) returning -ECONNABORTED every-other
# time that it's called. This applies most to OpenBSD
# whose sockets generate this errno more reproducibly than
# Linux and other OS.
if sock.accept.call_count % 2 == 0:
raise ConnectionAbortedError
return (mock.Mock(), mock.Mock())
sock.accept.side_effect = mock_sock_accept
backlog = 100
# test that _accept_connection's loop calls sock.accept
# all 100 times, continuing past ConnectionAbortedError
# instead of unnecessarily returning early
mock_obj = mock.patch.object
with mock_obj(self.loop, '_accept_connection2') as accept2_mock:
self.loop._accept_connection(
mock.Mock(), sock, backlog=backlog)
# as in test_accept_connection_multiple avoid task pending
# warnings by using asyncio.sleep(0)
self.loop.run_until_complete(asyncio.sleep(0))
self.assertEqual(sock.accept.call_count, backlog)
class SelectorTransportTests(test_utils.TestCase):

View file

@ -0,0 +1,4 @@
Correct behavior of
:func:`!asyncio.selector_events.BaseSelectorEventLoop._accept_connection`
in handling :exc:`ConnectionAbortedError` in a loop. This improves
performance on OpenBSD.