AdmissionControl permits leak when HTTP/2 stream processes terminate with their connection
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- elixir
- 領域
- backend, networking
調査の方向性
Electric.Plug.ServeShapePlug.call/2 と AdmissionControl の permit の格納および ETS カウンターの更新から始め、次に Bandit の HTTP/2 stream 終了動作を調査します。外部から終了された stream に対する、提案された回帰テストを追加します。#4771 の GOAWAY パスと、正常完了との race も含めてください。permit が正確に 1 回だけ回収され、その後のリクエストが制限を取得できれば完了です。
索引モデルが issue の本文から書いたものです。
説明
Versions
- Electric: current
main - HTTP server: Bandit 1.12.0
- Protocol: HTTP/2
Bug description
AdmissionControl permits are released by an after block in Electric.Plug.ServeShapePlug.call/2:
try do
# Run the shape request
after
release_admission_permit()
end
The acquired permit is stored only in the request process dictionary:
Process.put(@admission_permit_key, {stack_id, kind})
This handles normal returns, exceptions, throws, and exits raised within the request process. It does not handle the request process being terminated by an external exit signal, because the process stops without executing the after block.
This matters under Bandit's HTTP/2 process model:
- Each HTTP/2 stream runs its Plug pipeline in a separate
Bandit.HTTP2.StreamProcess. - The stream process is linked to the HTTP/2 connection process.
- The stream process does not trap exits.
- When the connection process terminates with a non-normal reason, its active stream processes terminate immediately.
- Any AdmissionControl permits held by those processes remain counted in the AdmissionControl ETS table.
HTTP/2 connection termination can happen when:
- The peer closes the connection while streams are active.
- The socket encounters an error.
- The server closes the connection.
- The HTTP/2
max_requestsbehavior described in #4771 sends GOAWAY and closes the connection.
Electric's live requests may spend up to long_poll_timeout waiting for changes, so it is normal for an HTTP/2 connection to have active request processes when the connection disappears.
Because the AdmissionControl ETS table is owned independently of the request and connection processes, leaked counts do not recover when those processes terminate. Repeated connection closures can ratchet the :initial or :existing counter upward until the configured concurrency limit is exhausted. At that point, subsequent requests receive 503 concurrent_request_limit_exceeded despite there being fewer—or no—corresponding live request processes.
The counters only recover when AdmissionControl or the VM restarts.
Expected behavior
An AdmissionControl permit should be released exactly once when its owning request process terminates, regardless of whether the request:
- Returns normally.
- Raises or exits internally.
- Is terminated by an external exit signal.
- Is killed because its HTTP/2 connection closes.
AdmissionControl's counters should represent live permit holders rather than relying exclusively on cooperative cleanup by those holders.
Suggested fix
Associate each acquired permit with its owning request process and monitor that process.
Both normal release and owner-process termination should attempt to atomically claim the permit lease before decrementing the counter. Only the path that successfully claims the lease should perform the decrement. This provides exactly-once release when normal cleanup races with a :DOWN notification.
The current direct ETS counter updates are intentionally cheap, so the implementation should avoid serializing every stack through one global GenServer. Possible implementations include partitioned lease monitors or another ownership mechanism that preserves concurrency across stacks.
Making HTTP/2 stream processes trap connection exits would be a narrower workaround, but it would only defer cleanup until the request returns and would couple Electric to Bandit's process model. Owner-monitored permits address the underlying AdmissionControl invariant regardless of why a request process terminates.
Suggested regression test
- Start Bandit with HTTP/2 enabled.
- Configure an AdmissionControl limit of one.
- Open a live request that remains in its long-poll wait and verify the counter is one.
- Close the underlying HTTP/2 connection while the stream remains active.
- Wait for the stream process to terminate.
- Assert that the AdmissionControl counter returns to zero.
- Assert that another request can acquire the permit.
A second test should cover the GOAWAY path from #4771 by configuring a very small HTTP/2 max_requests value.
The test should also verify that normal request completion does not cause a double release if process-death cleanup races with the existing after block.
Acceptance criteria
- Admission permits are associated with their owning request processes.
- A permit is reclaimed when its owner terminates unexpectedly.
- Normal completion and owner-death cleanup cannot release the same permit twice.
- HTTP/2 connection closure with active streams does not leave AdmissionControl counters elevated.
- A regression test covers externally terminated HTTP/2 stream processes.
- #4771 remains the mitigation for avoiding request-count-driven HTTP/2 connection cycling; this issue does not reintroduce an HTTP/2 request cap.
- 主要言語
- TypeScript
- スター
- 10.4k
- フォーク
- 399
- PR マージ指標
- 30日以内にマージされた PR はありません
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
electric-sql/electric のほかの issue
-
camelToSnake and snakeCamelMapper() drop the underscore when a digit precedes a camelCase boundaryオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
electric-sql/electric#4855 ·
-
ShapeStream does not notify subscribers when it gives up on a non-Error value対応中かも @Tyagiquamar が 19 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
electric-sql/electric#4830 ·
-
@electric-sql/client throws SecurityError on import when the browser blocks site storage (typeof localStorage outside try)対応中かも @mohanedmoh が 37 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
electric-sql/electric#4826 · コメント 1 件 ·
-
agents-chat-starter: spawn PUT goes to the durable-streams proxy, so no entity is created and agents never wake対応中かも @adityavkk が 133 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
electric-sql/electric#4687 ·
-
Listen IP addressオープンfeature request triage
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
electric-sql/electric#4150 · コメント 1 件 ·
electric-sql/electric の issue をすべて見る
似ている issue
-
Bug: Broken RTL / BiDi text rendering when mixing Persian (Farsi) with English or numbers対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンbug
難易度 2/5 半日 初心者へのやさしさ 68/100
amosblomqvist/learnharness#758 ·
メンテナーはふだん 2 日以内に返信
-
Change Workspacesオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
bitfocus/companion-module-renewedvision-propresenter-api#134 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
rcpch/digital-growth-charts-react-component-library#304 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
dsh-market/dsh-market#1210 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
trustjonathan/STUDY-HUB#13 ·