Skip to content

Fix H2 window flow control bugs causing flaky CI timeouts#582

Merged
haga-rak merged 1 commit into
mainfrom
fix/h2-window-flow-control
Mar 20, 2026
Merged

Fix H2 window flow control bugs causing flaky CI timeouts#582
haga-rak merged 1 commit into
mainfrom
fix/h2-window-flow-control

Conversation

@haga-rak
Copy link
Copy Markdown
Owner

Summary

  • Stream window ignores negotiated SETTINGS_INITIAL_WINDOW_SIZE: New streams were always initialized with the hardcoded OverallWindowSize (65535) instead of the server's negotiated initial window size. When the server advertises a larger window (e.g. 1MB), the client exhausts 65535 bytes then blocks waiting for a stream-level WINDOW_UPDATE that the server never sends (it believes the stream still has plenty of window). This is the root cause of 30s timeouts on large request bodies (e.g. Check_Global_Health with 150KB).
  • Remote PeerSetting.WindowSize default corrected: Changed from 6291456 to 65535 to match the HTTP/2 spec default (RFC 9113 section 6.5.2).
  • _initDone guard fixed: Init() set _initDone = false instead of true, allowing double-initialization of the connection (duplicate preface + read/write loops).
  • Lost-wakeup race in WindowSizeHolder: When two concurrent UpdateWindowSize calls raced, only the one crossing the 0-to-positive boundary would wake a waiter. The other's increment was silently added without waking anyone, starving blocked streams.
  • StreamPool.ShouldWindowUpdate thread safety: Replaced bare += with Interlocked.Add and Interlocked.Exchange.
  • Test coverage: Added larger payload size (150002 * 16) to Http2ClientHandler.GetHttpMethods.

- Use negotiated SETTINGS_INITIAL_WINDOW_SIZE for new streams instead of
  hardcoded 65535 (OverallWindowSize). Streams created after the SETTINGS
  exchange were missing the server's initial window, causing the client to
  block on an exhausted stream window while the server believed it had
  plenty — root cause of 30s timeouts on large request bodies.
- Set Remote PeerSetting.WindowSize default to 65535 (RFC 9113 §6.5.2).
- Fix _initDone = false (should be true) in H2ConnectionPool.Init(),
  which allowed double-initialization of the connection.
- Fix lost-wakeup race in WindowSizeHolder: wake a waiter whenever the
  window is positive, not only on the ≤0 → >0 transition. Concurrent
  UpdateWindowSize calls could silently skip the wake.
- Make StreamPool.ShouldWindowUpdate thread-safe with Interlocked ops.
- Add larger payload (150002 * 16) to Http2ClientHandler test coverage.
@haga-rak haga-rak merged commit 54306e5 into main Mar 20, 2026
2 checks passed
@haga-rak haga-rak added the bug Something isn't working label Mar 20, 2026
@haga-rak haga-rak deleted the fix/h2-window-flow-control branch March 20, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant