From 169f503f9cc91533fd87819e93675e60b3e5a13f Mon Sep 17 00:00:00 2001 From: "Yves.Duprat" Date: Sat, 15 Aug 2026 14:21:43 +0200 Subject: [PATCH 1/5] Initial commit: - add a private ``_index`` attr - add a test Co-authored-by: Sreehari Annam --- Lib/asyncio/locks.py | 8 ++++++-- Lib/test/test_asyncio/test_locks.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index c59ea15111bef2..ec3abc55aa8af7 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -489,6 +489,7 @@ def __init__(self, parties): self._parties = parties self._state = _BarrierState.FILLING self._count = 0 # count tasks in Barrier + self._index = self._parties - 1 # index number returned when barrier drains def __repr__(self): res = super().__repr__() @@ -515,13 +516,14 @@ async def wait(self): async with self._cond: await self._block() # Block while the barrier drains or resets. try: - index = self._count self._count += 1 - if index + 1 == self._parties: + if self._count == self._parties: # We release the barrier await self._release() else: await self._wait() + index = self._index + self._index = (self._index + 1) % self.parties return index finally: self._count -= 1 @@ -569,6 +571,7 @@ def _exit(self): if self._count == 0: if self._state in (_BarrierState.RESETTING, _BarrierState.DRAINING): self._state = _BarrierState.FILLING + self._index = self.parties - 1 self._cond.notify_all() async def reset(self): @@ -584,6 +587,7 @@ async def reset(self): self._state = _BarrierState.RESETTING else: self._state = _BarrierState.FILLING + self._index = self.parties - 1 self._cond.notify_all() async def abort(self): diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py index 320a933e144813..c6ab38a696a693 100644 --- a/Lib/test/test_asyncio/test_locks.py +++ b/Lib/test/test_asyncio/test_locks.py @@ -1820,6 +1820,35 @@ async def coro(): self.assertEqual(barrier1.n_waiting, 0) + async def test_filling_tasks_cancel_one_index_still_unique(self): + # See gh-155233: a task cancelled while the barrier is still + # filling used to leave its index available for reuse. + # Index counter is now calculated on demand during the draining phasis. + self.N = 3 + barrier = asyncio.Barrier(self.N) + results = [] + + async def coro(): + i = await barrier.wait() + results.append(i) + + t1 = asyncio.create_task(coro()) + t2 = asyncio.create_task(coro()) + await asyncio.sleep(0) + self.assertEqual(barrier.n_waiting, 2) + + t1.cancel() + with self.assertRaises(asyncio.CancelledError): + await t1 + await asyncio.sleep(0) + self.assertEqual(barrier.n_waiting, 1) + + t3 = asyncio.create_task(coro()) + t4 = asyncio.create_task(coro()) + await asyncio.gather(t2, t3, t4) + + self.assertEqual(sorted(results), list(range(self.N))) + self.assertEqual(barrier.n_waiting, 0) if __name__ == '__main__': unittest.main() From fce58bcf66360f5d5bfcfdd22cf6ee7691519c8b Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 10:59:34 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2026-10-07-10-59-27.gh-issue-155233.YGuQGK.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-10-07-10-59-27.gh-issue-155233.YGuQGK.rst diff --git a/Misc/NEWS.d/next/Library/2026-10-07-10-59-27.gh-issue-155233.YGuQGK.rst b/Misc/NEWS.d/next/Library/2026-10-07-10-59-27.gh-issue-155233.YGuQGK.rst new file mode 100644 index 00000000000000..bc1e35d4116a85 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-07-10-59-27.gh-issue-155233.YGuQGK.rst @@ -0,0 +1 @@ +Fix the returned value of the :meth:`asyncio.Barrier.wait`. This value is now calculated on demand during the *draining* phasis. From 25291d9bcba6b521bc8160a35211100f2f892d9f Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 5 Nov 2026 11:13:53 +0100 Subject: [PATCH 3/5] Apply suggestions from code review --- Lib/asyncio/locks.py | 9 +++++---- Lib/test/test_asyncio/test_locks.py | 1 + .../2026-10-07-10-59-27.gh-issue-155233.YGuQGK.rst | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index ec3abc55aa8af7..502d237edef0f3 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -489,7 +489,8 @@ def __init__(self, parties): self._parties = parties self._state = _BarrierState.FILLING self._count = 0 # count tasks in Barrier - self._index = self._parties - 1 # index number returned when barrier drains + # index number returned when barrier drains + self._index = self._parties - 1 def __repr__(self): res = super().__repr__() @@ -523,7 +524,7 @@ async def wait(self): else: await self._wait() index = self._index - self._index = (self._index + 1) % self.parties + self._index = (self._index + 1) % self._parties return index finally: self._count -= 1 @@ -571,7 +572,7 @@ def _exit(self): if self._count == 0: if self._state in (_BarrierState.RESETTING, _BarrierState.DRAINING): self._state = _BarrierState.FILLING - self._index = self.parties - 1 + self._index = self._parties - 1 self._cond.notify_all() async def reset(self): @@ -587,7 +588,7 @@ async def reset(self): self._state = _BarrierState.RESETTING else: self._state = _BarrierState.FILLING - self._index = self.parties - 1 + self._index = self._parties - 1 self._cond.notify_all() async def abort(self): diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py index c6ab38a696a693..f0e779c7a80409 100644 --- a/Lib/test/test_asyncio/test_locks.py +++ b/Lib/test/test_asyncio/test_locks.py @@ -1850,5 +1850,6 @@ async def coro(): self.assertEqual(sorted(results), list(range(self.N))) self.assertEqual(barrier.n_waiting, 0) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-10-07-10-59-27.gh-issue-155233.YGuQGK.rst b/Misc/NEWS.d/next/Library/2026-10-07-10-59-27.gh-issue-155233.YGuQGK.rst index bc1e35d4116a85..ebcd4a4bc29a5c 100644 --- a/Misc/NEWS.d/next/Library/2026-10-07-10-59-27.gh-issue-155233.YGuQGK.rst +++ b/Misc/NEWS.d/next/Library/2026-10-07-10-59-27.gh-issue-155233.YGuQGK.rst @@ -1 +1 @@ -Fix the returned value of the :meth:`asyncio.Barrier.wait`. This value is now calculated on demand during the *draining* phasis. +Fix :meth:`asyncio.Barrier.wait` returning duplicate indices when a waiting task is cancelled before the barrier is released.``` From 869ee88f0728024d16de6e15f2dee0a370294cc7 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 5 Nov 2026 11:22:20 +0100 Subject: [PATCH 4/5] Update Lib/asyncio/locks.py --- Lib/asyncio/locks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index 502d237edef0f3..d6dcc1fa5387ff 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -489,7 +489,7 @@ def __init__(self, parties): self._parties = parties self._state = _BarrierState.FILLING self._count = 0 # count tasks in Barrier - # index number returned when barrier drains + # index number returned when barrier drains self._index = self._parties - 1 def __repr__(self): From bfa94668088e814591ef7d07465db69bc34901e9 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 5 Nov 2026 11:31:15 +0100 Subject: [PATCH 5/5] Update Lib/asyncio/locks.py --- Lib/asyncio/locks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index d6dcc1fa5387ff..f2cf7c72ee8ed3 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -490,7 +490,7 @@ def __init__(self, parties): self._state = _BarrierState.FILLING self._count = 0 # count tasks in Barrier # index number returned when barrier drains - self._index = self._parties - 1 + self._index = self._parties - 1 def __repr__(self): res = super().__repr__()