classification
Title: Cancelling tasks waiting for asyncio.Conditions crashes w/ RuntimeError: Lock is not acquired.
Type: Stage: resolved
Components: asyncio Versions: Python 3.10, Python 3.9, Python 3.8
process
Status: closed Resolution: fixed
Dependencies: Superseder: asyncio.wait_for does not wait for task/future to be completed in all cases
View: 41891
Assigned To: Nosy List: asvetlov, hynek, lukasz.langa, piro, uriyyo, yselivanov
Priority: normal Keywords: patch

Created on 2020-12-08 12:35 by hynek, last changed 2021-03-07 23:37 by piro. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 23829 closed uriyyo, 2020-12-17 23:50
Messages (5)
msg382732 - (view) Author: Hynek Schlawack (hynek) * (Python committer) Date: 2020-12-08 12:35
This is something I've been procrastinating on for almost a year and working around it using my own version of asyncio.Condition because I wasn't sure how to describe it. So here's my best take:

Consider the following code:

```
import asyncio


async def tf(con):
    async with con:
        await asyncio.wait_for(con.wait(), 60)


async def f(loop):
    con = asyncio.Condition()

    t = loop.create_task(tf(con))

    await asyncio.sleep(1)
    t.cancel()

    async with con:
        con.notify_all()

    await t


loop = asyncio.get_event_loop()
loop.run_until_complete(f(loop))
```

(I'm using old-school APIs because I wanted to verify whether it was a regression. I ran into the bug with new-style APIs: https://gist.github.com/hynek/387f44672722171c901b8422320e8f9b)

`await t` will crash with:


```
Traceback (most recent call last):
  File "/Users/hynek/t.py", line 6, in tf
    await asyncio.wait_for(con.wait(), 60)
  File "/Users/hynek/.asdf/installs/python/3.9.0/lib/python3.9/asyncio/tasks.py", line 466, in wait_for
    await waiter
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/hynek/t.py", line 24, in <module>
    loop.run_until_complete(f(loop))
  File "/Users/hynek/.asdf/installs/python/3.9.0/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/Users/hynek/t.py", line 20, in f
    await t
  File "/Users/hynek/t.py", line 6, in tf
    await asyncio.wait_for(con.wait(), 60)
  File "/Users/hynek/.asdf/installs/python/3.9.0/lib/python3.9/asyncio/locks.py", line 20, in __aexit__
    self.release()
  File "/Users/hynek/.asdf/installs/python/3.9.0/lib/python3.9/asyncio/locks.py", line 146, in release
    raise RuntimeError('Lock is not acquired.')
RuntimeError: Lock is not acquired.

```

If you replace wait_for with a simple await, it works and raises an asyncio.exceptions.CancelledError:

```
Traceback (most recent call last):
  File "/Users/hynek/t.py", line 6, in tf
    await con.wait()
  File "/Users/hynek/.asdf/installs/python/3.9.0/lib/python3.9/asyncio/locks.py", line 290, in wait
    await fut
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/hynek/t.py", line 20, in f
    await t
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/hynek/t.py", line 24, in <module>
    loop.run_until_complete(f(loop))
  File "/Users/hynek/.asdf/installs/python/3.9.0/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
asyncio.exceptions.CancelledError
```

I have verified, that this has been broken at least since 3.5.10. The current 3.10.0a3 is affected too.
msg383264 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2020-12-17 20:27
Hey Hynek! :) Can you submit a PR?
msg383274 - (view) Author: Yurii Karabas (uriyyo) * (Python triager) Date: 2020-12-17 23:57
Hey Yurii, I have fixed this issue.
The actual problem was that `wait_for` cancels passed future but doesn't wait until it finished.
msg383309 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2020-12-18 17:28
This has been actually first fixed in #41891 but somehow wasn't yet merged. Yurii, thanks so much for working on this and making a PR, there was just another PR to fix the same issue that was there first, so I had to merge that one.
msg388253 - (view) Author: Daniele Varrazzo (piro) * Date: 2021-03-07 23:37
I have stumbled in this bug, or something similar, implementing psycopg3 async connection pool. A function such as the following fails but only in Python 3.6 (tested 3.6.12):

    async def wait(self, timeout: float) -> AsyncConnection:
        """Wait for a connection to be set and return it.
        Raise an exception if the wait times out or if fail() is called.
        """
        async with self._cond:
            if not (self.conn or self.error):
                try:
                    await asyncio.wait_for(self._cond.wait(), timeout)
                except asyncio.TimeoutError:
                    # HERE
                    self.error = PoolTimeout(
                        f"couldn't get a connection after {timeout} sec"
                    )

In python 3.6, printing self._cond.locked() in the HERE position gives False, even if it's inside the with block. Everything grinds to a halt afterwards.

However I don't have the same problem in other Python versions (3.7.10, 3.8.5 among the ones tested) so I'm not sure it is the same issue.

Is this a manifestation of the same bug or a different one? Is there any workaround or should I make the async pool just not supported on Python 3.6?
History
Date User Action Args
2021-03-07 23:37:37pirosetnosy: + piro
messages: + msg388253
2020-12-18 17:28:20yselivanovsetstatus: open -> closed
superseder: asyncio.wait_for does not wait for task/future to be completed in all cases
messages: + msg383309

resolution: fixed
stage: patch review -> resolved
2020-12-17 23:57:38uriyyosetmessages: + msg383274
2020-12-17 23:50:25uriyyosetkeywords: + patch
nosy: + uriyyo

pull_requests: + pull_request22689
stage: patch review
2020-12-17 20:27:30yselivanovsetmessages: + msg383264
2020-12-08 12:35:56hynekcreate