-
Notifications
You must be signed in to change notification settings - Fork 119
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
asyncio races on responses #195
Comments
yakshaver2000
pushed a commit
to yakshaver2000/python-mpd2
that referenced
this issue
Feb 9, 2023
When two commands were issued exactly 0.1 seconds apart, then `mpd.asyncio.MPDClient.__run` would incorrectly send an "idle" command to the server, and then attempt to parse the server's response to the second command as an "idle" response, causing errors like ``` mpd.base.ProtocolError: Expected key 'volume', got 'repeat' ``` as described by Mic92#195, and hangs as described by Mic92#173. The root of the problem is that wrapping `asyncio.Queue.get` in `asyncio.wait_for` can result in a `TimeoutError` exception even when the queue is not empty, as demonstrated by the following example (tested with python 3.9.2 and 3.11.2): ```python import asyncio TIMEOUT = 0.1 async def get_from_queue(queue): try: await asyncio.wait_for( queue.get(), timeout=TIMEOUT ) except asyncio.exceptions.TimeoutError: # This is counterintuitive: The "get" operation has timed out, # but the queue is not empty! assert not queue.empty() else: # This block is never executed. assert False async def main(): queue = asyncio.Queue() task = asyncio.create_task(get_from_queue(queue)) await asyncio.sleep(TIMEOUT) queue.put_nowait(1) await task asyncio.run(main()) ```
yakshaver2000
added a commit
to yakshaver2000/python-mpd2
that referenced
this issue
Feb 9, 2023
When two commands were issued exactly 0.1 seconds apart, then `mpd.asyncio.MPDClient.__run` would incorrectly send an "idle" command to the server, and then attempt to parse the server's response to the second command as an "idle" response, causing errors like ``` mpd.base.ProtocolError: Expected key 'volume', got 'repeat' ``` as described by Mic92#195, and hangs as described by Mic92#173. The root of the problem is that wrapping `asyncio.Queue.get` in `asyncio.wait_for` can result in a `TimeoutError` exception even when the queue is not empty, as demonstrated by the following example (tested with python 3.9.2 and 3.11.2): ```python import asyncio TIMEOUT = 0.1 async def get_from_queue(queue): try: await asyncio.wait_for( queue.get(), timeout=TIMEOUT ) except asyncio.exceptions.TimeoutError: # This is counterintuitive: The "get" operation has timed out, # but the queue is not empty! assert not queue.empty() else: # This block is never executed. assert False async def main(): queue = asyncio.Queue() task = asyncio.create_task(get_from_queue(queue)) await asyncio.sleep(TIMEOUT) queue.put_nowait(1) await task asyncio.run(main()) ```
Has been fixed. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It is possible to construct a rapid sequence of MPD commands that will confuse asyncio client with response order. Attached example most often makes it parse
status
response while expecting anidle
response, which leads to a crash. Sometimes I saw the reverse, whenidle
output comes when a client expectsstatus
output, but it is pretty rare.version:
python-mpd2 3.0.3
Here's sample output of the script below, with some extra debugging added to the library:
script to reproduce:
The text was updated successfully, but these errors were encountered: