Skip to content

Preserve orderly HTTP/2 duplex shutdown. - #248

Merged
samuel-williams-shopify merged 13 commits into
mainfrom
fix-http2-proxy-close
Sep 1, 2026
Merged

Preserve orderly HTTP/2 duplex shutdown.#248
samuel-williams-shopify merged 13 commits into
mainfrom
fix-http2-proxy-close

Conversation

@samuel-williams-shopify

@samuel-williams-shopify samuel-williams-shopify commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Align HTTP/2 application-facing input and output closure with HTTP/1 and duplex stream semantics.

  • The input and output directions close independently. Ending local output does not implicitly cancel remote input.
  • Closing an HTTP/2 input marks the application receive side as closed. Incoming DATA is discarded while restoring flow-control credit as long as local output remains active.
  • Once both application-facing directions are closed, the remaining wire stream is terminated with RST_STREAM(NO_ERROR) rather than treating normal closure as cancellation.
  • A received NO_ERROR reset is treated as orderly closure. Streamable bodies observe input EOF and unwind normally instead of having their task cancelled.
  • Closing the owning HTTP/2 response remains an explicit fast cancellation and sends RST_STREAM(CANCEL) while its body is active. Input errors use INTERNAL_ERROR.
  • Body::Pipe preserves the same independent-direction model: closing its write side closes the HTTP output without opportunistically stopping HTTP input.

The tests cover these contracts at their semantic boundaries:

  • Shared proxy behavior verifies under HTTP/1.0, HTTP/1.1, and HTTP/2 that closing the local peer finishes the request direction while an idle response remains active. If later response data cannot be forwarded to the closed peer, the response direction closes and the pooled resource is released.
  • Shared streamable-body behavior verifies under HTTP/1 and HTTP/2 that an echo completes and the server streaming block exits normally when its peer closes. This captures the same lifecycle required by async-websocket without depending on WebSocket framing.
  • HTTP/2-specific coverage confirms that closing input while output remains active discards incoming data without blocking the opposite direction.

Testing

  • Full async-http suite: 264 passed, 3 skipped, 32,919 assertions.
  • Focused proxy suite: 30 passed, 84 assertions.
  • Focused pipe suite: 4 passed, 3 assertions.
  • RuboCop over all tracked Ruby files: 120 files inspected, no offenses.
  • git diff --check: clean.

Types of Changes

  • Bug fix.

Contribution

  • I added tests for my changes.
  • I tested my changes locally.
  • I agree to the Developer Certificate of Origin 1.1.

Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
@samuel-williams-shopify samuel-williams-shopify changed the title Close abandoned HTTP/2 proxy tunnels. Preserve orderly HTTP/2 duplex shutdown. Sep 1, 2026
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
Assisted-By: devx/618580b0-d55f-4c2e-95b6-87e648f60543
@samuel-williams-shopify
samuel-williams-shopify merged commit 0085d9d into main Sep 1, 2026
34 of 46 checks passed
@samuel-williams-shopify
samuel-williams-shopify deleted the fix-http2-proxy-close branch September 1, 2026 10:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant