@@ -200,6 +200,56 @@ async def lockit(name, blocker):
200200 self .assertTrue (tb .cancelled ())
201201 self .assertTrue (tc .done ())
202202
203+ def test_cancel_release_race (self ):
204+ # Issue 32734
205+ # Acquire 4 locks, cancel second, release first
206+ # and 2 locks are taken at once.
207+ lock = asyncio .Lock (loop = self .loop )
208+ lock_count = 0
209+ call_count = 0
210+
211+ async def lockit ():
212+ nonlocal lock_count
213+ nonlocal call_count
214+ call_count += 1
215+ await lock .acquire ()
216+ lock_count += 1
217+
218+ async def lockandtrigger ():
219+ await lock .acquire ()
220+ self .loop .call_soon (trigger )
221+
222+ def trigger ():
223+ t1 .cancel ()
224+ lock .release ()
225+
226+ t0 = self .loop .create_task (lockandtrigger ())
227+ t1 = self .loop .create_task (lockit ())
228+ t2 = self .loop .create_task (lockit ())
229+ t3 = self .loop .create_task (lockit ())
230+
231+ # First loop acquires all
232+ test_utils .run_briefly (self .loop )
233+ self .assertTrue (t0 .done ())
234+
235+ # Second loop calls trigger
236+ test_utils .run_briefly (self .loop )
237+ # Third loop calls cancellation
238+ test_utils .run_briefly (self .loop )
239+
240+ # Make sure only one lock was taken
241+ self .assertEqual (lock_count , 1 )
242+ # While 3 calls were made to lockit()
243+ self .assertEqual (call_count , 3 )
244+ self .assertTrue (t1 .cancelled () and t2 .done ())
245+
246+ # Cleanup the task that is stuck on acquire.
247+ t3 .cancel ()
248+ test_utils .run_briefly (self .loop )
249+ self .assertTrue (t3 .cancelled ())
250+
251+
252+
203253 def test_finished_waiter_cancelled (self ):
204254 lock = asyncio .Lock (loop = self .loop )
205255
0 commit comments