summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjb2170 <email@jb2170.com>2025-01-03 10:32:36 (GMT)
committerGitHub <noreply@github.com>2025-01-03 10:32:36 (GMT)
commit830e10651b1f45cd0af36ff611397b9f53171220 (patch)
tree3984d016964a91bdfa4b158b593c0ff494afcfd2
parentbb2dfadb9221fa3035fda42a2c153c831013e3d3 (diff)
downloadcpython-830e10651b1f45cd0af36ff611397b9f53171220.zip
cpython-830e10651b1f45cd0af36ff611397b9f53171220.tar.gz
cpython-830e10651b1f45cd0af36ff611397b9f53171220.tar.bz2
gh-127529: Correct asyncio's `accept_connection` behaviour for handling `ConnectionAbortedError` (#127532)
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
-rw-r--r--Lib/asyncio/selector_events.py10
-rw-r--r--Lib/test/test_asyncio/test_selector_events.py25
-rw-r--r--Misc/NEWS.d/next/Library/2024-12-02-19-13-19.gh-issue-127529.Pj1Xtf.rst4
3 files changed, 36 insertions, 3 deletions
diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
index f1ab9b1..50992a6 100644
--- a/Lib/asyncio/selector_events.py
+++ b/Lib/asyncio/selector_events.py
@@ -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,
diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py
index f984dc9..c9217d0 100644
--- a/Lib/test/test_asyncio/test_selector_events.py
+++ b/Lib/test/test_asyncio/test_selector_events.py
@@ -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):
diff --git a/Misc/NEWS.d/next/Library/2024-12-02-19-13-19.gh-issue-127529.Pj1Xtf.rst b/Misc/NEWS.d/next/Library/2024-12-02-19-13-19.gh-issue-127529.Pj1Xtf.rst
new file mode 100644
index 0000000..26f2fd5
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-12-02-19-13-19.gh-issue-127529.Pj1Xtf.rst
@@ -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.