HTTP/2 Data padding is not counted toward the connection flow-control window. This deadlocks connections with peers that pad.
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 67/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- kotlin
- 領域
- networking
調査の方向性
Start with Http2Reader.readData, Http2Connection.updateConnectionFlowControl, and Http2Stream.FramingSource.receive to trace how DATA frame bytes reach connection-level accounting. Add a regression test in Http2ConnectionTest that sends padded DATA frames and reads the stream-0 WINDOW_UPDATE; done when its increment accounts for the full padded payload, as RFC 9113 requires.
索引モデルが issue の本文から書いたものです。
説明
Summary
Http2Reader does not count DATA frame padding toward the connection-level flow-control window. When the peer pads its DATA frames, the server's view of the connection window drains faster than the client's counter grows, the server's window reaches zero before the client reaches its next WINDOW_UPDATE threshold, and the connection deadlocks. PING/PONG keeps working, so the connection stays in the pool and every subsequent call on it times out.
Observed on 5.5.0; the code is unchanged on main.
Where
Http2Reader.readData reads the pad-length byte, computes the body length without padding, calls handler.data(..., dataLength) with the body length only, and then skips the padding:
https://github.com/lysine-dev/okhttp/blob/e9eae50d36e938603811a41f9e2ab04182bcb515/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http2/Http2Reader.kt#L198-L202
Http2Connection.updateConnectionFlowControl is therefore only ever fed the body length, both through the stream path (Http2Stream.FramingSource.receive) and the unknown-stream path:
https://github.com/lysine-dev/okhttp/blob/e9eae50d36e938603811a41f9e2ab04182bcb515/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http2/Http2Connection.kt#L189
https://github.com/lysine-dev/okhttp/blob/e9eae50d36e938603811a41f9e2ab04182bcb515/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http2/Http2Stream.kt#L517
The pad-length byte and the padding bytes are consumed from the socket but never credited back to the peer.
Spec
RFC 9113 §6.1: "The entire DATA frame payload is included in flow control, including the Pad Length and Padding fields if present."
https://www.rfc-editor.org/rfc/rfc9113.html#section-6.1
Go's net/http2 fixed the same bug in 2016 and refunds padding immediately on receipt (golang/go#16556, https://go.googlesource.com/net/+/35028a49ca5a73b486af60cd20ac21cd6b67bfdb). Google's quiche fixed a padding flow-control leak that stalled Envoy connections (envoyproxy/envoy#32401, https://quiche.googlesource.com/quiche/+/9e875938052443c7f51b25012093be59e20598ac). Netty charges data.readableBytes() + padding.
How it shows up in practice
Google Front End pads every response DATA frame with a random 0–255 bytes. A production client of ours talking to an API behind GFE deadlocked every long-lived connection at the same point. Measured with a FlowControlListener on the pool:
| response body | ~250 B |
| DATA frame on the wire | ~372 B average, uniform 247–503 B (`DATA END_STREAM |
| fraction the client counts | ~0.665 |
| client window | 16 MiB advertised, WINDOW_UPDATE of 8 MiB once 8 MiB has been counted |
| server bytes sent at the freeze | 24.0 MiB (16 MiB initial + one 8 MiB grant) |
WindowCounter at the freeze |
total 15.7–16.7 MiB, acknowledged 8,388,6xx: below the threshold for the second grant |
| responses per connection before the freeze | ~67,000 |
After the freeze the only inbound frames were PINGs and the server's own WINDOW_UPDATEs for our request bytes. No HEADERS or DATA, no GOAWAY, no RST_STREAM. Http2Connection.isHealthy() kept returning true, so the connection was reused for every call until the server eventually closed it.
Failing test
The Http2Writer used by the test peer cannot write padded frames, so the test writes DATA frames by hand: a 9-byte frame header with FLAG_PADDED, one pad-length byte, the body, then the padding. Send enough frames for the padded total to exceed half the client window, then read the WINDOW_UPDATE the client sends on stream 0. Expected: the increment equals the padded bytes received. Actual on 5.5.0: the increment equals the body bytes only. (I can turn this into a test in Http2ConnectionTest if that helps.)
Workaround
Restricting the affected client to HTTP/1.1 removes the problem entirely.
- 主要言語
- Kotlin
- スター
- 47.1k
- フォーク
- 9.3k
- 平均マージ
- 18時間 18分
- マージ済み PR(30日)
- 15
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
lysine-dev/okhttp のほかの issue
-
難易度 1/5 1時間未満 初心者へのやさしさ 85/100
lysine-dev/okhttp#9788 ·
メンテナーはふだん 1 日以内に返信
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
lysine-dev/okhttp#9761 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
Remove crossinline `addInterceptor` and `addNetworkInterceptor` overloads再び着手できるかも @Amir-Golmoradi が 7 日前に担当しましたが、オープン中のプルリクエストはありません。 オープンenhancement
難易度 3/5 1〜2日 初心者へのやさしさ 58/100
lysine-dev/okhttp#9779 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 55/100
lysine-dev/okhttp#9760 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
Unexpected Hostname Causing Crash in Android再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープンbug
難易度 2/5 1〜3時間 初心者へのやさしさ 35/100
lysine-dev/okhttp#9758 · リアクション 4 件 ·
メンテナーはふだん 1 日以内に返信
lysine-dev/okhttp の issue をすべて見る
似ている issue
-
Dev
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
ankidroid/Anki-Android#22555 ·
メンテナーはふだん 1 日以内に返信
-
enhancement good first issue help wanted
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
-
core: RAC_LLM_OPTIONS_DEFAULT.min_p disagrees with the IDL's annotated default対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンbug core p3
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
RunanywhereAI/runanywhere-sdks#1083 ·
メンテナーはふだん 5 日以内に返信
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
メンテナーはふだん 1 日以内に返信
-
Java and Kotlin (JVM): parse gradle/wrapper/gradle-wrapper.properties with exact line locationsオープンarea: detector eco: java help wanted lane: free-agent roadmap size: S type: parser
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
arminazimi/evergreen#355 ·
メンテナーはふだん 1 日以内に返信