[Bug] WebSocket Streams: after a connection drops, resubscribing to the same stream on the same client sends nothing
まだ誰も着手していません。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 82/100
調査の方向性
common/src/binance_common/websocket.py、特に _discard_connection() と _forget_connection_streams() を読み、その後、issue に記載された再現手順をローカルの WebSocket サーバーに対して実行してください。接続が切断された後、同じクライアントで再度購読すると SUBSCRIBE が送信され、復旧更新が届くことを確認してください。issue では、_discard_connection() の最後で切断された接続のストリームを忘れることを提案しています。
索引モデルが issue の本文から書いたものです。
説明
Follow-up to #570, which common v4.6.0 fixed for two clients in one process. With a single client, one case from the same stream map remains: after a stream connection drops unexpectedly, subscribing to the same stream again on a new connection does nothing. No SUBSCRIBE is sent, nothing is raised, nothing is logged about the skipped stream, and no messages arrive, so the stream looks like a quiet market. Recovering through a new Spot client works in 4.6.0.
When the server closes the connection or the TCP connection drops, the receive loop fires close and calls _discard_connection() (lines 414, 421 and 428), which removes the connection from the pool (line 497). The client's stream_connections still maps the stream to the dead connection, because _forget_connection_streams() runs only after a failed planned reconnect (line 907) and on a user-initiated close (line 1138). On the next subscribe, subscribe() filters the stream out as already registered (lines 1288-1292), and on() attaches the callback to the dead connection (lines 1361-1367). Spot.websocket_streams is cached, and create_connection() on it returns the same instance, so the new connection shares the stale map. Line numbers are from common/src/binance_common/websocket.py at a54ec0b, which matches the 4.6.0 release.
Reproduce
binance-sdk-spot==13.2.0, binance-common==4.6.0, aiohttp==3.14.5, Python 3.14.8. A local aiohttp server drops the first connection's TCP without a close frame. The client then opens a new connection on the same Spot client and subscribes to the same stream again. A close frame (1001) in place of the TCP drop gives the same result.
Reproduction script and output
import asyncio, json, logging
from aiohttp import web
from binance_common.configuration import ConfigurationWebSocketStreams
from binance_sdk_spot.spot import Spot
logging.disable(logging.CRITICAL)
conns, subs = [], []
async def handler(request):
ws = web.WebSocketResponse(); await ws.prepare(request)
conns.append(request); conn = len(conns)
async for msg in ws:
d = json.loads(msg.data)
if d["method"] == "SUBSCRIBE":
subs.append((conn, d["params"]))
await ws.send_json({"id": d["id"], "result": None})
for s in d["params"]:
await ws.send_json({"stream": s, "data": {"e": "depthUpdate", "E": 1, "s": "BNBUSDT", "U": conn, "u": conn, "b": [], "a": []}})
if conn == 1:
await asyncio.sleep(0.2)
request.transport.abort() # the TCP connection drops, no close frame
return ws
async def main():
app = web.Application(); app.router.add_get("/stream", handler)
r = web.AppRunner(app); await r.setup(); await web.TCPSite(r, "127.0.0.1", 18767).start()
client = Spot(config_ws_streams=ConfigurationWebSocketStreams(stream_url="ws://127.0.0.1:18767/stream"))
got, events = [], []
ws = await client.websocket_streams.create_connection()
ws.on_connection("close", lambda *a: events.append("close"))
(await ws.diff_book_depth(symbol="bnbusdt")).on("message", lambda m: got.append(("first", m.U)))
await asyncio.sleep(0.5)
# recover: a new connection on the same client, same stream
ws2 = await client.websocket_streams.create_connection()
(await ws2.diff_book_depth(symbol="bnbusdt")).on("message", lambda m: got.append(("recovery", m.U)))
await asyncio.sleep(0.5)
print("same instance:", ws2 is ws)
print("connections accepted by the server:", len(conns))
print("connection events:", events)
print("SUBSCRIBE frames received (connection, params):", subs)
print("messages delivered:", got)
await ws2.close_connection(close_session=True); await r.cleanup()
asyncio.run(main())
Output:
same instance: True
connections accepted by the server: 2
connection events: ['close']
SUBSCRIBE frames received (connection, params): [(1, ['bnbusdt@depth'])]
messages delivered: [('first', 1)]
Expected
The second connection sends SUBSCRIBE for bnbusdt@depth, and the recovery callback receives the second connection's update.
Suggested fix
Call self._forget_connection_streams(connection) at the end of _discard_connection(), after its early return for planned reconnects. Its docstring already describes this case: "A connection that is gone for good must not be left in the stream maps, or a later subscribe would treat its streams as still live and silently skip them." With that call patched in, the script above shows one SUBSCRIBE per connection and delivers ('recovery', 2).
- 主要言語
- Python
- スター
- 3k
- フォーク
- 712
- 平均マージ
- 1日 5時間
- マージ済み PR(30日)
- 4
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
binance/binance-connector-python のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
binance/binance-connector-python#570 · コメント 2 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
binance/binance-connector-python#514 · コメント 3 件 ·
-
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
-
難易度 4/5 3〜5日 初心者へのやさしさ 15/100
binance/binance-connector-python#575 · コメント 1 件 ·
-
[Bug] Response models declare numeric fields as strict `str`, causing silent fallback of `.data()` to raw dict (simple-earn & staking)対応中かも @Tyagiquamar が 33 日前に担当しました。 オープン
難易度 4/5 3〜5日 初心者へのやさしさ 68/100
binance/binance-connector-python#553 · コメント 2 件 ·
binance/binance-connector-python の issue をすべて見る
似ている issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
ansys/pyansys-common-mcp#213 ·
メンテナーはふだん 3 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 67/100
freedomofpress/dangerzone#1594 ·
メンテナーはふだん 3 日以内に返信
-
priority:P1 status:in-review type:bug
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
randomparity/kdive#3317 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
area:ci
難易度 1/5 1時間未満 初心者へのやさしさ 90/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
mesonbuild/meson#16281 ·
メンテナーはふだん 1 日以内に返信