-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
bpo-32734: Fix asyncio.Lock multiple acquire safety issue #5466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -183,16 +183,22 @@ async def acquire(self): | |
|
||
fut = self._loop.create_future() | ||
self._waiters.append(fut) | ||
|
||
# Finally block should be called before the CancelledError | ||
# handling as we don't want CancelledError to call | ||
# _wake_up_first() and attempt to wake up itself. | ||
try: | ||
await fut | ||
self._locked = True | ||
return True | ||
try: | ||
await fut | ||
finally: | ||
self._waiters.remove(fut) | ||
except futures.CancelledError: | ||
if not self._locked: | ||
self._wake_up_first() | ||
raise | ||
finally: | ||
self._waiters.remove(fut) | ||
|
||
self._locked = True | ||
return True | ||
|
||
def release(self): | ||
"""Release a lock. | ||
|
@@ -212,11 +218,17 @@ def release(self): | |
raise RuntimeError('Lock is not acquired.') | ||
|
||
def _wake_up_first(self): | ||
"""Wake up the first waiter who isn't cancelled.""" | ||
for fut in self._waiters: | ||
if not fut.done(): | ||
fut.set_result(True) | ||
break | ||
"""Wake up the first waiter if it isn't done.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if it is done? Can there be a situation where you have [fut, fut] in the _waiters list? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There can be indeed, when 2 or more locks are waiting on acquire. In summary: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, makes sense. Could you please add this all as a comment to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Phew, now that's a long comment, lemme see how can I shorten it while still managing to fully explain it. Challenge accepted :P There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, if you have time to write a short comment :) Sorry for being anal about this. Every once in a while we find a new bug in There was a problem hiding this comment. 8000Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nah, all good mate. It took some time to wrap my head around it as well. How's this comment? Attempting to be short and to the point as much as you can with synchronization. |
||
try: | ||
fut = next(iter(self._waiters)) | ||
except StopIteration: | ||
return | ||
|
||
# .done() necessarily means that a waiter will wake up later on and | ||
# either take the lock, or, if it was cancelled and lock wasn't | ||
# taken already, will hit this again and wake up a new waiter. | ||
if not fut.done(): | ||
fut.set_result(True) | ||
|
||
|
||
class Event: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Fixed ``asyncio.Lock()`` safety issue which allowed acquiring and locking | ||
the same lock multiple times, without it being free. Patch by Bar Harel. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very subtle. Can you add a comment why we use two nested 'try' statements here? I.e. that it's important that 'finally' block is called before 'except CancelledError'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing :-)