Client State Machine
The Client class uses a state machine to manage its internal state,
and wherever possible, prefers callbacks to asyncio tasks. While this makes
the code a bit more complex, it is easier to reason about correctness because
the state is represented explicitly (as an enum) rather than by the position
of the instruction pointer within a coroutine (or several).
The following states are used
- CONNECTING
We’re establishing the TCP connection.
- NEGOTIATING
We’ve established the TCP connection and are waiting for
#katcp-protocol.- CONNECTED
We’re fully connected.
- DISCONNECTING
We have started disconnecting from our side, but the connection still exists.
- SLEEPING
We’re sleeping between reconnection attempts.
- CLOSED
There is no connection and we are not going to try again (either because
Client.close()was called or because auto-reconnect is disabled).
The possible transitions between these states are depicted below. To keep the
picture readable, the SLEEPING and CLOSED states are represented
together. Transitions to these states will transition to SLEEPING if
auto-reconnection is enabled and CLOSED if not (calling
Client.close internally disables auto-reconnection). There are no
transitions from CLOSED.

Transitions marked in red above will call the failed connection callbacks, provided that there is an exception to report. Transitions to CONNECTED trigger connected callbacks, while transitions from CONNECTED trigger disconnected callbacks.
Invariants
The following invariants are maintained at any time the event loop runs or
when calling user callbacks. Most of the work of ensuring this occurs in
Client._set_state(). These invariants are checked by
ClientStateMachine in tests/test_client.py.
Client.is_connectedis true iff the state is CONNECTED.Client._closed_eventis set iff the state is CLOSED.Client._connectionisNoneiff the state isCONNECTING,SLEEPINGorCLOSED.Client._disconnected_eventis set iffClient._connectionisNone.Client._connect_taskis set iff the state isCONNECTING.Client._sleep_handleis set iff the state isSLEEPING.If the state is
DISCONNECTING, thenClient._connection.is_closing()is true (the reverse is not true: a fatal I/O error on the connection will schedule aasyncio.BaseProtocol.connection_lost()call for the next event loop iteration).In states DISCONNECTING, CLOSED and SLEEPING,
Client.last_excwill be set.In state CONNECTED,
Client.last_excwill beNone.