Skip to content

Count DATA frame padding toward HTTP/2 flow control - #9786

Open
Amir-Golmoradi wants to merge 7 commits into
lysine-dev:mainfrom
Amir-Golmoradi:fix-http2-data-padding-flow-control
Open

Amir-Golmoradi wants to merge 7 commits into
lysine-dev:mainfrom
Amir-Golmoradi:fix-http2-data-padding-flow-control

Conversation

@Amir-Golmoradi

@Amir-Golmoradi Amir-Golmoradi commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #9772.

Http2Reader.readData skips DATA padding without telling anyone, so the Pad Length byte and padding bytes are never counted for flow control. RFC 9113 section 6.1 says the whole payload counts against the window. The peer subtracts those bytes from its send window as it transmits them, but we never send a WINDOW_UPDATE for them, so the peer's view of the window keeps running down until it thinks there's no room left and stops sending.

I added Handler.dataPadding(streamId, length), called after the padding is skipped (Pad Length byte included). It only fires when length > dataLength, so unpadded frames are unchanged.

In ReaderRunnable it goes to updateConnectionFlowControl and, if the stream still exists, to a new FramingSource.receivePadding, which bumps readBytes right away since the app will never read those bytes. Unknown and removed streams get connection credit for the padding. Pushed streams get connection credit only, since there's no Http2Stream yet.

The reported stall is connection-level, but the same bytes come out of both windows, so I did both. Happy to split it if you prefer.

Tests cover padding of 0, 39 and 255, truncated padding, padding-only frames, and the unknown, removed and pushed-stream paths. MockHttp2Peer now sends padded frames with sendPaddedData, since Http2Writer can't.

The HTTP/2 tests and spotlessCheck pass.

@JakeWharton

Copy link
Copy Markdown
Collaborator

LLM text is forbidden. This is your only warning.

@Amir-Golmoradi

Copy link
Copy Markdown
Contributor Author

Hi @JakeWharton, hope you have great day so far.

I must say that i only used an LLM to draft the PR description and write a comment on the issue. That was wrong, and I'm sorry but because of the project that i was working on i wanted to "everything be perfect" to respect maintainer's time. i'd never wanted to disrespect the project's rules.

I've written the code and tests myself, and I can explain change.
I'll write future PR descriptions and comments myself.

Would you be open to a new PR with a description I write myself? If you'd rather I didn't submit one, I understand.

@JakeWharton

Copy link
Copy Markdown
Collaborator

I will reopen, and you can edit this one down to normal. Thanks.

@JakeWharton JakeWharton reopened this Oct 12, 2026
@Amir-Golmoradi

Copy link
Copy Markdown
Contributor Author

Hi @JakeWharton , hope you're doing well.

Sorry for the late reply. I was reworking the PR description to match the project's guidelines, and it's now updated. Could you please take another look?

}

@ParameterizedTest
@ValueSource(ints = [0, 39, 255])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice tests

@dimitris-selal

Copy link
Copy Markdown

@Amir-Golmoradi PR is approved. You can merge this

@Amir-Golmoradi

Copy link
Copy Markdown
Contributor Author

@dimitris-selal

I didn’t know that; I thought one of the maintainers had to do it, since two of the workflows still need approval from the maintainers.

@dimitris-selal

Copy link
Copy Markdown

@Amir-Golmoradi some checks are failing. Can you check please?

@Amir-Golmoradi

Copy link
Copy Markdown
Contributor Author

@dimitris-selal
I'm on it, Thanks for flagging it.

@Amir-Golmoradi

Copy link
Copy Markdown
Contributor Author

openjdklatest and jvm (17) passed after the latest push.

The remaining jvm (21) failure is a 30-second timeout in DuplexTest.duplexWithRedirect. After updating my local branch to 4a582dc94, I ran the individual test, the full DuplexTest class, and ./gradlew test allTests -Ptest.java.version=21 --rerun-tasks --info. All passed locally with JDK 21 and Gradle 9.8.1.

I couldn't reproduce the timeout or establish its cause. Could you rerun jvm (21)?

@Amir-Golmoradi

Copy link
Copy Markdown
Contributor Author

@yschimke thanks for the approval. I can't merge this myself (no write access),
so could you merge it or re-run the failed jobs?

Latest CI run:

  • jvm (21), openjdklatest and the other JVM/Android jobs pass now. Earlier failures
    (DuplexTest.duplexWithRedirect and Http2ConnectionTest.discardedDataFramesAreCounted)
    did not repeat.
  • checks failed with "Bnd build failed" in :okhttp-urlconnection:jar:
    "Invalid syntax for version: unspecified". My change does not touch that module,
    and the task passes on my machine.

Let me know if you want me to change anything.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

HTTP/2 Data padding is not counted toward the connection flow-control window. This deadlocks connections with peers that pad.

4 participants