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

Đang mở
#940 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

@beshkenadze đang làm issue này rồi.

Từ ngày 22/10/2026.

  • #943 của @beshkenadze — đang mở

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
55/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
swift
Lĩnh vực
backend, networking

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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
Ngôn ngữ chính
Swift
Star
1.1k
Fork
159
Merge trung bình
1 giờ 22 phút
Pull request đã merge (30 ngày)
3

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của swift-server/async-http-client

Tất cả issue của swift-server/async-http-client

Issue tương tự

Thêm issue về Swift

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.