Skip to content
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 client disconnected from reality #217

Open
orbisvicis opened this issue Aug 7, 2023 · 1 comment
Open

asyncio client disconnected from reality #217

orbisvicis opened this issue Aug 7, 2023 · 1 comment

Comments

@orbisvicis
Copy link

The connected property is rather meaningless. There's no rhyme or reason to its value. Test output, and test program:

# async sleep + submit command + fetch results
#   1691326137 : main: connected
#   1691326257 : main: slept (disconnected)
#   1691326257 : main: connected:  True
#   1691326257 : main: submitted status
#   1691326257 : main: connected:  True
#   1691326257 : main: awaiting command result succeeded
#   1691326257 : main: connected:  True
#   1691326257 : main: done
#
# time sleep + submit command + fetch results
#   1691326306 : main: connected
#   1691326426 : main: slept (disconnected)
#   1691326426 : main: connected:  True
#   1691326426 : main: submitted status
#   1691326426 : main: connected:  True
#   1691326426 : main: awaiting command result failed:  ConnectionError('Connection lost while reading line')
#   1691326426 : main: connected:  False
#   1691326426 : main: done
#
# submit command + async sleep + fetch results
#   1691326541 : main: connected
#   1691326541 : main: submitted status
#   1691326541 : main: connected:  True
#   1691326661 : main: slept (disconnected)
#   1691326661 : main: connected:  True
#   1691326661 : main: awaiting command result succeeded
#   1691326661 : main: connected:  True
#   1691326661 : main: done
#
# submit command + time sleep + fetch result
#   1691326721 : main: connected
#   1691326721 : main: submitted status
#   1691326721 : main: connected:  True
#   1691326841 : main: slept (disconnected)
#   1691326841 : main: connected:  True
#   1691326841 : main: awaiting command result succeeded
#   1691326841 : main: connected:  True
#   1691326841 : main: done

# time sleep + submit command + reconnect + fetch results + submit/fetch again
#   1691327990 : main: connected
#   1691328110 : main: slept (disconnected)
#   1691328110 : main: connected:  True
#   1691328110 : main: submitted status
#   1691328110 : main: connected:  True
#   1691328110 : main: re-connected
#   1691328110 : main: connected:  True
#   1691328110 : main: awaiting command result failed:  ConnectionError('Connection lost while reading line')
#   1691328110 : main: connected:  True
#   1691328110 : main: submitted status
#   1691328110 : main: awaiting command result succeeded
#   1691328110 : main: done
#
# time sleep + submit command + reconnect + time sleep + fetch results + submit/fetch again
#   1691328676 : main: connected
#   1691328796 : main: slept (disconnected)
#   1691328796 : main: connected:  True
#   1691328796 : main: submitted status
#   1691328796 : main: connected:  True
#   1691328796 : main: re-connected
#   1691328796 : main: connected:  True
#   1691328916 : main: slept (disconnected)
#   1691328916 : main: connected:  True
#   1691328916 : main: awaiting command result failed:  ConnectionError('Connection lost while reading line')
#   1691328916 : main: connected:  True
#   1691328916 : main: submitted status
#   1691328916 : main: connected:  True
#   1691328916 : main: awaiting command result failed:  ConnectionError('Connection lost while reading line')
#   1691328916 : main: connected:  False
#   1691328916 : main: done
import time
import pprint
import asyncio
import mpd.asyncio


def gt():
    return str(int(time.time())) + " :"

async def main():
    client = mpd.asyncio.MPDClient()
    await client.connect("localhost", 6600)
    print(gt(), "main: connected")

    #await asyncio.sleep(120)
    time.sleep(120)
    print(gt(), "main: slept (disconnected)")

    c = client.connected
    print(gt(), "main: connected: ", c)

    f = client.status()
    print(gt(), "main: submitted status")

    c = client.connected
    print(gt(), "main: connected: ", c)

    await client.connect("localhost", 6600)
    print(gt(), "main: re-connected")

    c = client.connected
    print(gt(), "main: connected: ", c)

    time.sleep(120)
    print(gt(), "main: slept (disconnected)")

    c = client.connected
    print(gt(), "main: connected: ", c)

    try:
        await f
    except Exception as e:
        print(gt(), "main: awaiting command result failed: ", repr(e))
    else:
        print(gt(), "main: awaiting command result succeeded")

    c = client.connected
    print(gt(), "main: connected: ", c)

    f = client.status()
    print(gt(), "main: submitted status")

    c = client.connected
    print(gt(), "main: connected: ", c)

    try:
        await f
    except Exception as e:
        print(gt(), "main: awaiting command result failed: ", repr(e))
    else:
        print(gt(), "main: awaiting command result succeeded")

    c = client.connected
    print(gt(), "main: connected: ", c)

    print(gt(), "main: done")


asyncio.run(main())
@orbisvicis
Copy link
Author

The issue is this. Imagine a pool of asynchronous tasks each submitting unrelated MPD commands. The pool grows as new tasks come in. I know MPD is a serial protocol so the main (library) mpd task linearizes the requests, but otherwise the tasks are independent. Now for whatever reason the client is disconnected, so let's follow the queue of MPD commands.

The first few commands interrupted mid-response will raise ProtocolError and be retried, bumped to the end of the queue.

The next commands will have no response and raise ConnectionError. They'll await a new connection and then retry the command, bumped to the end of the queue. The point is that for each disconnected command beyond the first I'll be making a redundant reconnection.

Then we get to the commands issued after the first successful re-connection. They'd have been successful if I hadn't reconnected again. Perhaps they'll work, or perhaps they'll raise a ProtocolError, but worst-case-scenario they raise ConnectionError and trigger another re-connection. It'll be an avalanche of connection errors.

The main mpd task needs to track the connection state to prevent unnecessary connections. I really hope I'm wrong and that there's a better way to do this, but right now I'm tracking the time I submit each MPD command and each time I reconnect to reconstitute the current connection state. But please - correct me if there's a better way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant