gh-155233: Fix asyncio.Barrier reusing a cancelled task's index - #155235
Open
sreehariannam wants to merge 1 commit into
Open
gh-155233: Fix asyncio.Barrier reusing a cancelled task's index#155235sreehariannam wants to merge 1 commit into
sreehariannam wants to merge 1 commit into
Conversation
Barrier.wait() assigned each task's index eagerly at arrival, from a counter that also gets decremented when a task leaves early (e.g. via cancellation) while the barrier is still filling. A later arrival could then be assigned the same index an already-waiting task from the same round had already captured, so two tasks in one successful release could receive the same index while another index in range was never handed out -- violating the documented "unique index from 0 to parties-1" guarantee. Indices are now finalized only once a round's exact release cohort is known (at release time), assigned once from current arrival order, so an early departure can no longer collide with a still-waiting task.
sreehariannam
requested review from
1st1,
asvetlov,
kumaraditya303 and
willingc
as code owners
August 5, 2026 11:17
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.
Fixes gh-155233.
Barrier.wait()is documented to return "a unique and individual index number from 0 toparties-1" for each task that passes the barrier together. If a task'swait()is cancelled while the barrier is still filling (i.e. before enough parties have arrived for that round), a task that arrives afterward can be assigned the same index as another task already waiting in that same round:On current
main:BandCboth get index1; index0is never handed out among the three tasks (B,C,D) that actually pass the barrier together. Full analysis and root cause are in gh-155233.Root cause
index = self._countwas computed eagerly at arrival, before a round's final membership was known.self._countis also decremented when a party leaves early (cancellation) during filling, so a later arrival could land on the exact value an already-waiting task had already captured as its own index.Fix
Indices can only be assigned correctly once a round's exact release cohort is known, so this defers assignment to release time:
object()) toself._present, an ordered list of currently-present parties for the round._release()(called by the last arriving task) buildsself._release_index = {ticket: i for i, ticket in enumerate(self._present)}— a snapshot of arrival order for exactly the parties in that round — before any of them get a chance to leave.self._count/n_waiting/reset()'s occupancy check are now derived fromlen(self._present)instead of a separately-tracked int.threading.Barrierdoesn't need equivalent handling since a synchronous thread can't be cancelled out of a blocking wait the way anasyncio.Taskcan, so this is specific to the asyncio implementation.Testing
test_filling_tasks_cancel_one_index_not_reused, which fails against the old implementation ([1, 1, 2]instead of[0, 1, 2]) and passes against the fix.test_reset_barrier_when_tasks_half_draining) that reached into the now-renamed privatebarrier._countattribute.test.test_asynciosuite (2783 tests) passes.