Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

[Bug] WebSocket Streams: after a connection drops, resubscribing to the same stream on the same client sends nothing

オープン 初心者向け
#576 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
82/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
python
領域
api, backend

調査の方向性

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 を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

binance/binance-connector-python のほかの issue

binance/binance-connector-python の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。