HTTP/2 stream stays half-closed after an early response with status >= 300

未关闭
#940 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

@beshkenadze 已经在做这个了。

开始于 2026年10月22日。

  • #943 来自 @beshkenadze —— 未关闭

评估

难度
4/5
预计耗时
3-5 天
新手友好度
55/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
swift

调研方向

Start with HTTP2ClientRequestHandler.runSuccessfulFinalAction and the state machine path described in the issue; compare how .close is handled for an early response while the request body is incomplete. Run swift test --filter 'HTTP2ClientRequestHandlerTests/testStreamIs' using the added tests in Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift. Done means the client sends RST_STREAM(CANCEL), the stream is released, and shutdown() completes.

由索引模型根据 Issue 内容生成。

描述

AsyncHTTPClient commit hash: 017115279d09dbfd262a6c678e96d31c90b3b2aa (main).

Context

After an early complete response with status >= 300 (RFC 9113, section 8.1), the client sends neither END_STREAM nor RST_STREAM. The stream stays half-closed (remote), and the connection counts it as open. HTTPClient.shutdown() does not complete until the connection closes. Status < 300 is a different case: #922.

Example: apple/containerization sends each blob PUT without credentials first, and the registry answers 401 during the upload.

Cause:

  1. The request body is not complete. HTTPRequestStateMachine returns .forwardResponseEnd(.close, ...).
  2. Since #657, HTTP2ClientRequestHandler.runSuccessfulFinalAction ignores .close, because "an h2 stream is closed after every request anyway". This is not true here. The request body stream is paused, and the .finished state machine rejects more body parts and the request end.
  3. HTTP2IdleHandler gets no StreamClosedEvent for this stream.
  4. At shutdown(), the stream gets HTTPConnectionEvent.shutdownRequested. The request is finished, so requestCancelled() returns .wait.
Steps to reproduce

The repro sends one 64 MiB PUT without credentials to registry-1.docker.io over HTTP/2. The registry answers 401 during the upload.

git clone https://github.com/beshkenadze/async-http-client-early-response-leak
cd async-http-client-early-response-leak
AHC_REVISION=0171152 ./run.sh   # AHC main

The README has the commands for 1.36.2, a lazy body and --wait 660, the output fields and the requirements.

The tests use only in-memory channels. git and SwiftPM need network access. The commands add only the fix branch's new tests to main:

git clone https://github.com/swift-server/async-http-client.git
cd async-http-client
git checkout 017115279d09dbfd262a6c678e96d31c90b3b2aa
git fetch https://github.com/beshkenadze/async-http-client.git fix/h2-reset-stream-after-early-response
git checkout FETCH_HEAD -- Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift
swift test --filter 'HTTP2ClientRequestHandlerTests/testStreamIs'
Expected result

The client sends RST_STREAM(CANCEL). The connection releases the stream, and shutdown() completes.

Actual result
Run openH2Streams shutdown()
main and 1.36.2, eager and lazy body, 2 runs each 1, in 8 of 8 runs Does not complete in 10 s, in 8 of 8 runs.
1.36.2, eager body, --wait 660, 1 run The stream and the connection closed together 61 s after the start of the run. Completed at once after the 660 s wait.

The fix for #939 does not change these results in 4 runs. The raw data names that build local@afcf468. Its Sources/ tree (6b69592) is the same as in beshkenadze:fix/early-response-request-body-stream.

Test Result
testStreamIsClosedIfErrorResponseEndsBeforeRequestBodyIsSent Fails. After an early 401, the stream channel is still active (XCTAssertFalse failed).
testStreamIsResetIfErrorResponseEndsBeforeRequestBodyIsSent Fails. The client sends no RST_STREAM: XCTAssertEqual failed: ("[]") is not equal to ("[HTTP2ErrorCode<0x8 Cancel>]")
The other 2 of the 4 tests Pass.

Raw data:

Environment

swift-nio-http2 1.46.0, macOS 27.0.1 (26A434).

$ swift --version
swift-driver version: 1.168.6 Apple Swift version 6.4 (swiftlang-6.4.0.34.1 clang-2100.3.34.1)
Target: arm64-apple-macosx27.0.0
$ uname -srvm
Darwin 27.0.0 Darwin Kernel Version 27.0.0: Tue Aug 11 21:22:49 PDT 2026; root:xnu-13432.1.9~1/RELEASE_ARM64_T6000 arm64
主要语言
Swift
星标
1.1k
派生
159
平均合并
2 小时 56 分钟
30 天内合并 PR
1

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 阅读贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

swift-server/async-http-client 的其他 Issue

查看 swift-server/async-http-client 的全部 Issue

相似的 Issue

更多 Swift Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。