Skip to content

Default max_queue blocks websocket cancellation with high traffic #1555

Closed
@btschwertfeger

Description

@btschwertfeger

I'm working on the python-kraken-sdk and encounter strange behavior, where the websocket connection doesn't get properly closed when the connection is continuously receiving messages filling up the queue (max_queue (https://websockets.readthedocs.io/en/stable/reference/asyncio/client.html#module-websockets.asyncio.client).

This happens locally (ubuntu 24.04, Python 3.11.9) and in CI (GitHub actions, windows-latest, ubuntu-latest, py3.11 and py3.12, e.g. https://github.com/btschwertfeger/python-kraken-sdk/actions/runs/12016162113 and https://github.com/btschwertfeger/python-kraken-sdk/actions/runs/12016429517)

I'm using websockets==14.1.

In the following is the log from Ubuntu (and other runs when having max_queue not touched):

INFO     tests.spot.helper:helper.py:97 ["BTC/USD", {"channel": "book", "type": "update", "data": [{"symbol": "BTC/USD", "bids": [{"price": 95398.3, "qty": 0.25}], "asks": [], "checksum": 329006635, "timestamp": "2024-11-25T18:10:51.622559Z"}]}]
DEBUG    websockets.client:protocol.py:745 > CLOSE 1000 (OK) [2 bytes]
DEBUG    websockets.client:protocol.py:175 = connection is CLOSING
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.656589Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.656612Z"}]}' [181 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.660735Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.660780Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.660828Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.661035Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.661093Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.661109Z"}]}' [181 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.665449Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.666942Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.667265Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.667316Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.667336Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.667410Z"}]}' [216 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.669715Z"}]}' [252 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.675411Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.675436Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.675518Z"}]}' [252 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T18:10:51.675575Z"}]}' [181 bytes]
DEBUG    websockets.client:connection.py:799 % sending keepalive ping
=========================== short test summary info ============================
FAILED tests/spot/test_spot_orderbook.py::test_add_book - Failed: Timeout >60.0s

... when setting the max_queue to None (not recommended by the documentation), everything is fine:

INFO     tests.spot.helper:helper.py:97 ["BTC/USD", {"channel": "book", "type": "update", "data": [{"symbol": "BTC/USD", "bids": [{"price": 94794.3, "qty": 0.2}], "asks": [], "checksum": 3009784427, "timestamp": "2024-11-25T19:55:47.910197Z"}]}]
DEBUG    websockets.client:protocol.py:745 > CLOSE 1000 (OK) [2 bytes]
DEBUG    websockets.client:protocol.py:175 = connection is CLOSING
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.911987Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.912155Z"}]}' [252 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.912337Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.912350Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.915588Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.918207Z"}]}' [181 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.918814Z"}]}' [181 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.918849Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.918940Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.919616Z"}]}' [252 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.920569Z"}]}' [252 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.922159Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.922502Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.923636Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.929364Z"}]}' [251 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.929945Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.930777Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.930900Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.930995Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.932799Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.932858Z"}]}' [251 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.934082Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.934498Z"}]}' [252 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.934839Z"}]}' [218 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.938315Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.938505Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.938646Z"}]}' [252 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.939445Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.939583Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.940378Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.941481Z"}]}' [252 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.942796Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.947044Z"}]}' [252 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.947067Z"}]}' [251 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.949671Z"}]}' [251 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.952241Z"}]}' [217 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.952430Z"}]}' [182 bytes]
DEBUG    websockets.client:protocol.py:599 < TEXT '{"channel":"book","type":"update","data":[{"sym...25T19:55:47.953434Z"}]}' [252 bytes]
DEBUG    websockets.client:protocol.py:599 < CLOSE 1000 (OK) [2 bytes]
DEBUG    websockets.client:protocol.py:649 < EOF
DEBUG    websockets.client:protocol.py:757 > EOF
DEBUG    websockets.client:protocol.py:175 = connection is CLOSED
DEBUG    websockets.client:connection.py:960 x closing TCP connection
WARNING  kraken.spot.websocket.connectors:connectors.py:259 Connection closed

I'm not that into how the websockets implementation of yours work, but it doesn't seem to respect connections with high traffic. Proper cancellation should be done in any case, regardless of the size or number of queued messages.

This can be reproduced by running the following:

git clone https://github.com/btschwertfeger/python-kraken-sdk
cd python-kraken-sdk
git switch 
uv venv --pyhton=3.11
source .venv/bin/activate
uv pip install ".[test]"
python3 -m pytest -vv --log-cli-level=DEBUG tests/spot/test_spot_orderbook.py::test_add_book

One may need to run this test multiple times in order to get the situation with too much messages during cancellation. An alternative would be to add more books to the test case (e.g. await orderbook.add_book(pairs=["BTC/USD", "DOT/USD", "ETH/USD", "MATIC/USD", "BTC/EUR"])).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions