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

Shared-storage topics are never freed: streaming leaks ~32 frames per page load

オープン
#4,010 コメント 0 件 リアクション 0 件 担当者 1 名 GitHub で見る

メンテナーはふだん 2 日以内に返信

@T4rk1n がすでに取り組んでいます。

2026年9月24日 から。

評価

この issue はまだ評価されていません。

説明

bug P1 size: 3

Describe the bug

Shared-storage pub/sub topics are never removed. The streaming transport (#3930/#3931) uses one topic per page load (stream_topic(connection_id) in dash/_stream_hub.py), and each topic keeps its last buffer_size (default 32) frames. So every page load that runs a streaming callback leaves its last 32 frames in memory for the life of the process.

With LocalSharedStorage, StoreEngine._topics (dash/_shared_storage/_engine.py) is a plain dict. _topic() adds an entry on first publish, head_seq or poll, and nothing ever deletes one. Pub/sub is not persisted, so the only thing that frees them is the owner process exiting.

Measured

A streaming callback yielding 40 frames of ~50 KB, driven headless with dash_duo: 20 page loads, then the browser navigates away and we wait 40s (past DOWNLINK_GRACE and POLL_GRACE).

  • 20 _dash_stream: topics still in engine._topics
  • 640 frames still buffered (32 per topic)
  • heap grew 39.9 MB, about 2 MB per page load

A long-running app streaming figures or LLM output grows without bound.

Other backends

  • DiskcacheSharedStorage: ss:seq:<topic> / ss:msg:<topic>:<n> are written without expire. Bounded only by the cache size_limit eviction.
  • RedisSharedStorage: the seq counter and stream keys have no TTL. MAXLEN limits messages per topic, not the number of topics.

App code using ctx.shared_storage.publish/subscribe with per-session or per-run topic names leaks the same way (about 2.4 KB per empty topic on the local engine).

Expected behavior

A topic nobody uses any more is released. Some options:

  • Local engine: drop a topic once it has no subscribers or waiters and has been idle past some window.
  • Add delete_topic(topic) to BaseSharedStorage and call it when a Downlink connection is finished (the pump already knows when the browser is gone).
  • A topic ttl, matching set(..., ttl=): Redis EXPIRE, diskcache expire=, a deadline in the local engine.

Streaming and shared storage are not released yet (4.5.0rc0), so this can be fixed before 4.5.0.

Repro

import asyncio
from dash import Dash, Input, Output, html

app = Dash(__name__)
app.layout = html.Div([html.Button("go", id="btn"), html.Div(id="out")])

@app.callback(Output("out", "children"), Input("btn", "n_clicks"),
              prevent_initial_call=True)
async def cb(n):
    for i in range(40):
        await asyncio.sleep(0.01)
        yield "x" * 50_000 + f"-{i}"
    yield "done"

Load the page and click the button N times across reloads, then inspect app.shared_storage._coord.engine._topics: one _dash_stream:<connection_id> entry per page load, each holding 32 frames, long after the browser left.

主要言語
Python
スター
24.4k
フォーク
2.3k
平均マージ
3日 10時間
マージ済み PR(30日)
24

環境構築

はじめの一歩

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

plotly/dash のほかの issue

plotly/dash の issue をすべて見る

似ている issue

Python の issue をもっと見る

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

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