You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
asyncio.create_unix_server has an off-by-one error concerning the backlog parameter #90871
Hi, asyncio.create_unix_server appears to treat the "backlog" parameter as where 0 means that *no connection will ever possibly be pending*, which (at the very least for UNIX sockets on my machine) is untrue.
When run against the server example, the first invocation of this client will actually connect successfully (Surprising, but that's how the C syscalls work too, so... alright) but the second invocation of this client will raise BlockingIOError (EAGAIN).
Further, if we amend the first server example to actually call accept(), it will succeed when the first client connects -- demonstrating that the actual total queue length here was actually effectively 1, not 0.
(i.e. there's always room for at least one connection to be considered, and the backlog counts everybody else.)
However, in asyncio.BaseSelectorEventLoop._accept_connection(...), the code uses for _ in range(backlog) to determine the maximum number of accept calls to make. When backlog is set to zero, this means we will never call accept, even when there are pending connections.
Note that when backlog=1, this actually allows for *two* pending connections before clients are rejected, but this loop will only fire once. This behavior is surprising, because backlog==0 means we'll accept no clients, but backlog==1 means we will allow for two to enqueue before accepting both. There is seemingly no way with asyncio to actually specify "Exactly one pending connection".
I think this loop should be amended to reflect the actual truth of the backlog parameter, and it should iterate over backlog + 1. This does necessitate a change to [Lib/test/test_asyncio/test_selector_events.py](https://github.com/python/cpython/blob/main/Lib/test/test_asyncio/test_selector_events.py) which believes that backlog=100 means that accept() should be called 100 times (instead of 101.)
A (very) simple fix is attached here; if it seems sound, I can spin a real PR on GitHub.
Next action: Create a test for backlog 0 and 1 behavior.
Whoops, I forgot about this one, my apologies... If you haven't picked this up already, what are steps I need to take to submit a contribution? IIRC, there's a CLA ...? (Or something like a CLA...?)
Activity
jnsnow commentedon Feb 10, 2022
Hi, asyncio.create_unix_server appears to treat the "backlog" parameter as where 0 means that *no connection will ever possibly be pending*, which (at the very least for UNIX sockets on my machine) is untrue.
Consider a (non-asyncio) server:
This server never calls accept(), and uses a backlog of zero. However, a client can actually still successfully call connect against such a server:
When run against the server example, the first invocation of this client will actually connect successfully (Surprising, but that's how the C syscalls work too, so... alright) but the second invocation of this client will raise BlockingIOError (EAGAIN).
Further, if we amend the first server example to actually call accept(), it will succeed when the first client connects -- demonstrating that the actual total queue length here was actually effectively 1, not 0.
(i.e. there's always room for at least one connection to be considered, and the backlog counts everybody else.)
However, in asyncio.BaseSelectorEventLoop._accept_connection(...), the code uses
for _ in range(backlog)to determine the maximum number of accept calls to make. When backlog is set to zero, this means we will never call accept, even when there are pending connections.Note that when backlog=1, this actually allows for *two* pending connections before clients are rejected, but this loop will only fire once. This behavior is surprising, because backlog==0 means we'll accept no clients, but backlog==1 means we will allow for two to enqueue before accepting both. There is seemingly no way with asyncio to actually specify "Exactly one pending connection".
I think this loop should be amended to reflect the actual truth of the backlog parameter, and it should iterate over
backlog + 1. This does necessitate a change to[Lib/test/test_asyncio/test_selector_events.py](https://github.com/python/cpython/blob/main/Lib/test/test_asyncio/test_selector_events.py)which believes that backlog=100 means that accept() should be called 100 times (instead of 101.)A (very) simple fix is attached here; if it seems sound, I can spin a real PR on GitHub.
12 remaining items
jnsnow commentedon Jun 26, 2024
Whoops, I forgot about this one, my apologies... If you haven't picked this up already, what are steps I need to take to submit a contribution? IIRC, there's a CLA ...? (Or something like a CLA...?)
willingc commentedon Jun 28, 2024
Hi @jnsnow, No worries. If you want to work on this, just leave a message here. The devguide.python.org has details on Python contribution.
Regarding the CLA, a bot will ask you to sign it when you submit a PR. https://devguide.python.org/getting-started/pull-request-lifecycle/#licensing
Thanks!
Merge branch 'pythongh-86802' into pythongh-90871
gh-90871: fix connection backlog offset in asyncio (gh-134392)
pythongh-90871: fix connection backlog offset in asyncio (pythongh-13…
[3.14] gh-90871: fix connection backlog offset in asyncio (gh-134392) (…
pythongh-90871: fix connection backlog offset in asyncio (pythongh-13…
pythongh-90871: fix connection backlog offset in asyncio (pythongh-13…
pythongh-90871: fix connection backlog offset in asyncio (pythongh-13…
pythongh-90871: fix connection backlog offset in asyncio (pythongh-13…
[3.13] gh-90871: fix connection backlog offset in asyncio (gh-134392) (…
pythongh-90871: fix connection backlog offset in asyncio (pythongh-13…