Skip to content

asyncio client disconnected from reality #217

Open

Description

@orbisvicis

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())

Activity

  1. orbisvicis commented on Aug 8, 2023

    @orbisvicis
    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions