Description
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"])
).