File tree Expand file tree Collapse file tree 3 files changed +37
-5
lines changed Expand file tree Collapse file tree 3 files changed +37
-5
lines changed Original file line number Diff line number Diff line change @@ -176,6 +176,10 @@ def acquire(self):
176
176
yield from fut
177
177
self ._locked = True
178
178
return True
179
+ except futures .CancelledError :
180
+ if not self ._locked :
181
+ self ._wake_up_first ()
182
+ raise
179
183
finally :
180
184
self ._waiters .remove (fut )
181
185
@@ -192,14 +196,17 @@ def release(self):
192
196
"""
193
197
if self ._locked :
194
198
self ._locked = False
195
- # Wake up the first waiter who isn't cancelled.
196
- for fut in self ._waiters :
197
- if not fut .done ():
198
- fut .set_result (True )
199
- break
199
+ self ._wake_up_first ()
200
200
else :
201
201
raise RuntimeError ('Lock is not acquired.' )
202
202
203
+ def _wake_up_first (self ):
204
+ """Wake up the first waiter who isn't cancelled."""
205
+ for fut in self ._waiters :
206
+ if not fut .done ():
207
+ fut .set_result (True )
208
+ break
209
+
203
210
204
211
class Event :
205
212
"""Asynchronous equivalent to threading.Event.
Original file line number Diff line number Diff line change @@ -176,6 +176,28 @@ def lockit(name, blocker):
176
176
self .assertTrue (tb .cancelled ())
177
177
self .assertTrue (tc .done ())
178
178
179
+ def test_finished_waiter_cancelled (self ):
180
+ lock = asyncio .Lock (loop = self .loop )
181
+
182
+ ta = asyncio .Task (lock .acquire (), loop = self .loop )
183
+ test_utils .run_briefly (self .loop )
184
+ self .assertTrue (lock .locked ())
185
+
186
+ tb = asyncio .Task (lock .acquire (), loop = self .loop )
187
+ test_utils .run_briefly (self .loop )
188
+ self .assertEqual (len (lock ._waiters ), 1 )
189
+
190
+ # Create a second waiter, wake up the first, and cancel it.
191
+ # Without the fix, the second was not woken up.
192
+ tc = asyncio .Task (lock .acquire (), loop = self .loop )
193
+ lock .release ()
194
+ tb .cancel ()
195
+ test_utils .run_briefly (self .loop )
196
+
197
+ self .assertTrue (lock .locked ())
198
+ self .assertTrue (ta .done ())
199
+ self .assertTrue (tb .cancelled ())
200
+
179
201
def test_release_not_acquired (self ):
180
202
lock = asyncio .Lock (loop = self .loop )
181
203
Original file line number Diff line number Diff line change @@ -56,6 +56,9 @@ Extension Modules
56
56
Library
57
57
-------
58
58
59
+ - bpo-27585: Fix waiter cancellation in asyncio.Lock.
60
+ Patch by Mathieu Sornay.
61
+
59
62
- bpo-30418: On Windows, subprocess.Popen.communicate() now also ignore EINVAL
60
63
on stdin.write() if the child process is still running but closed the pipe.
61
64
You can’t perform that action at this time.
0 commit comments