…ndow
The H2 handshake's SETTINGS-ACK wait loop discarded every non-ACK frame,
including a connection-level WINDOW_UPDATE(0, ...) the server may send in its
preface to grow the connection flow-control window above the RFC 7540 §6.9.2
floor of 65535. Dropping it stranded connSendWindow at 65535, deadlocking
sustained request-body sends (post-4k-h2 / post-64k-h2) after ~65535 bytes:
the server only replenishes against its larger configured window (e.g.
Kestrel's 1 MiB InitialConnectionWindowSize), a threshold the stranded client
never reaches.
ASP.NET/Kestrel sends this WINDOW_UPDATE in its preface (before its SETTINGS
ack), so aspnet-h2 post-4k measured 0 rps in v1.5.x; servers that send it
after the ack were unaffected (readLoop applied it).
Capture connection-level WINDOW_UPDATE grants during the handshake and seed
connSendWindow with them. Additive + safe: servers without a preface grant are
unchanged. Verified macOS + Linux/multipass: aspnet-h2 POST-4k 0 -> sustained
(1.1M / 673k req, 0 errors); h2c GET + h2 test suite unaffected.
Problem
aspnet-h2post-4k-h2 measured 0 rps in the v1.5.x benchmarks (suspect), while its get-json-h2 worked and every other framework's post-4k-h2 worked. Root cause is a loadgen h2 flow-control bug, not ASP.NET.Root cause
The h2 handshake's SETTINGS-ACK wait loop discarded every non-ACK frame — including a connection-level
WINDOW_UPDATE(0, …)the server sends in its preface to grow the connection window above the RFC 7540 §6.9.2 floor of 65535. Dropping it strandsconnSendWindowat 65535, which deadlocks sustained request-body sends after ~65535 bytes (~16× 4 KB/conn). Kestrel sends this WINDOW_UPDATE before its SETTINGS ack (caught + discarded); servers that send it after were unaffected.Fix
Capture connection-level WINDOW_UPDATE grants during the handshake and seed
connSendWindowwith them. Purely additive — servers without a preface grant are unchanged.Verification (before → after)
h2c GET and
go testh2 suite unaffected (regression-checked).