Repository navigation
Fix CapacityLimiter over-granting tokens on asyncio - #1172
Merged
agronholm merged 9 commits intoJul 12, 2026
Merged
Conversation
On the asyncio backend, releasing a token notified the next waiter but did not reserve the token for it until that waiter resumed. A non-blocking acquire issued in that window saw an empty wait queue and a borrower count below the limit, so it slipped through, leaving borrowed_tokens greater than total_tokens and available_tokens negative. Reserve the freed token for the woken waiter the moment its event is set (in _notify_next_waiter and when increasing total_tokens), mirroring the Semaphore/Lock hand-off, so the non-blocking acquire correctly raises WouldBlock. The woken waiter no longer re-adds itself on resume, and a waiter cancelled after being granted the token now releases it to the next in line. Fixes agronholm#1170.
agronholm
reviewed
Jul 9, 2026
Contributor
Author
|
The red The same job fails the same way on master — e.g. run 28965447285 and run 28957305269. It's timing-sensitive: the recorded durations across the last three runs here were 0.103 s, 0.118 s and 0.136 s. (The warning's 3959 tests pass otherwise. I can't re-run the job from a fork, so leaving it as-is. |
Now that agronholm#1218 makes the trio backend raise anyio.WouldBlock, the regression test no longer needs to be scoped to asyncio; the invariant holds on both backends.
Contributor
Author
2 of 3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
Fixes #1170.
On the asyncio backend,
CapacityLimitercould over-grant tokens: a non-blocking acquire issued in the window between a token being released (which notifies the next waiter) and that waiter actually resuming would slip through, leavingborrowed_tokens > total_tokensandavailable_tokensnegative — two tasks holding a single-token limiter at once.The cause:
release_on_behalf_ofpopped the next waiter out of_wait_queueand set its event, but the waiter only added itself to_borrowersafter it resumed. In between, the wait queue was empty and the borrower count was below the limit, soacquire_on_behalf_of_nowait's guard admitted a second borrower for the already-reserved token.The fix reserves the freed token for the woken waiter the moment its event is set — the borrower is added to
_borrowersin_notify_next_waiter(and whentotal_tokensis increased) rather than after the waiter resumes — mirroring the hand-off that already makesSemaphore/Lockimmune. Consequently:_borrowerson resume, andOnly the asyncio backend is affected; the trio backend wraps trio's own
CapacityLimiter.Checklist
If this is a user-facing code change, like a bugfix or a new feature, please ensure that
you've fulfilled the following conditions (where applicable):
tests/) which would fail without your patchdocs/), in case of behavior changes or newfeatures
docs/versionhistory.rst).If this is a trivial change, like a typo fix or a code reformatting, then you can ignore
these instructions.
This change was prepared by an AI agent under my direction; I reviewed and verified it.