python-mpd2 icon indicating copy to clipboard operation
python-mpd2 copied to clipboard

asyncio client disconnected from reality

Open orbisvicis opened this issue 2 years ago • 1 comments

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 avatar Aug 07 '23 12:08 orbisvicis

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.

orbisvicis avatar Aug 08 '23 06:08 orbisvicis