Skip to content

Commit b85590b

Browse files
asvetlovhello-adam
authored andcommitted
[3.9] bpo-45997: Fix asyncio.Semaphore re-acquiring order (pythonGH-31910) (pythonGH-32049)
Co-authored-by: Kumar Aditya <[email protected]>. (cherry picked from commit 32e7715) Co-authored-by: Andrew Svetlov <[email protected]>
1 parent ebe9354 commit b85590b

File tree

3 files changed

+36
-6
lines changed

3 files changed

+36
-6
lines changed

Lib/asyncio/locks.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ def __init__(self, value=1, *, loop=None):
378378
warnings.warn("The loop argument is deprecated since Python 3.8, "
379379
"and scheduled for removal in Python 3.10.",
380380
DeprecationWarning, stacklevel=2)
381+
self._wakeup_scheduled = False
381382

382383
def __repr__(self):
383384
res = super().__repr__()
@@ -391,6 +392,7 @@ def _wake_up_next(self):
391392
waiter = self._waiters.popleft()
392393
if not waiter.done():
393394
waiter.set_result(None)
395+
self._wakeup_scheduled = True
394396
return
395397

396398
def locked(self):
@@ -406,16 +408,17 @@ async def acquire(self):
406408
called release() to make it larger than 0, and then return
407409
True.
408410
"""
409-
while self._value <= 0:
411+
# _wakeup_scheduled is set if *another* task is scheduled to wakeup
412+
# but its acquire() is not resumed yet
413+
while self._wakeup_scheduled or self._value <= 0:
410414
fut = self._loop.create_future()
411415
self._waiters.append(fut)
412416
try:
413417
await fut
414-
except:
415-
# See the similar code in Queue.get.
416-
fut.cancel()
417-
if self._value > 0 and not fut.cancelled():
418-
self._wake_up_next()
418+
# reset _wakeup_scheduled *after* waiting for a future
419+
self._wakeup_scheduled = False
420+
except exceptions.CancelledError:
421+
self._wake_up_next()
419422
raise
420423
self._value -= 1
421424
return True

Lib/test/test_asyncio/test_locks.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,32 @@ async def test_release_no_waiters(self):
961961
sem.release()
962962
self.assertFalse(sem.locked())
963963

964+
async def test_acquire_fifo_order(self):
965+
sem = asyncio.Semaphore(1)
966+
result = []
967+
968+
async def coro(tag):
969+
await sem.acquire()
970+
result.append(f'{tag}_1')
971+
await asyncio.sleep(0.01)
972+
sem.release()
973+
974+
await sem.acquire()
975+
result.append(f'{tag}_2')
976+
await asyncio.sleep(0.01)
977+
sem.release()
978+
979+
t1 = asyncio.create_task(coro('c1'))
980+
t2 = asyncio.create_task(coro('c2'))
981+
t3 = asyncio.create_task(coro('c3'))
982+
983+
await asyncio.gather(t1, t2, t3)
984+
985+
self.assertEqual(
986+
['c1_1', 'c2_1', 'c3_1', 'c1_2', 'c2_2', 'c3_2'],
987+
result
988+
)
989+
964990

965991
if __name__ == '__main__':
966992
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :class:`asyncio.Semaphore` re-aquiring FIFO order.

0 commit comments

Comments
 (0)