Skip to content

Commit 18f6505

Browse files
committed
Fix crash if request is canceled after request header is send
1 parent 31cc312 commit 18f6505

File tree

3 files changed

+46
-5
lines changed

3 files changed

+46
-5
lines changed

Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1ClientChannelHandler.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,14 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler {
186186
case .sendRequestHead(let head, let sendEnd):
187187
self.sendRequestHead(head, sendEnd: sendEnd, context: context)
188188
case .notifyRequestHeadSendSuccessfully(let resumeRequestBodyStream, let startIdleTimer):
189-
189+
// We can force unwrap the request here, as we have just validated in the state machine,
190+
// that the request is neither failed nor finished yet
190191
self.request!.requestHeadSent()
191-
if resumeRequestBodyStream {
192-
self.request!.resumeRequestBodyStream()
192+
if resumeRequestBodyStream, let request = self.request {
193+
// The above request head send notification might lead the request to mark itself as
194+
// cancelled, which in turn might pop the request of the handler. For this reason we
195+
// must check if the request is still present here.
196+
request.resumeRequestBodyStream()
193197
}
194198
if startIdleTimer {
195199
if let timeoutAction = self.idleReadTimeoutStateMachine?.requestEndSent() {

Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,14 @@ final class HTTP2ClientRequestHandler: ChannelDuplexHandler {
143143
case .sendRequestHead(let head, let sendEnd):
144144
self.sendRequestHead(head, sendEnd: sendEnd, context: context)
145145
case .notifyRequestHeadSendSuccessfully(let resumeRequestBodyStream, let startIdleTimer):
146+
// We can force unwrap the request here, as we have just validated in the state machine,
147+
// that the request is neither failed nor finished yet
146148
self.request!.requestHeadSent()
147-
if resumeRequestBodyStream {
148-
self.request!.resumeRequestBodyStream()
149+
if resumeRequestBodyStream, let request = self.request {
150+
// The above request head send notification might lead the request to mark itself as
151+
// cancelled, which in turn might pop the request of the handler. For this reason we
152+
// must check if the request is still present here.
153+
request.resumeRequestBodyStream()
149154
}
150155
if startIdleTimer {
151156
if let timeoutAction = self.idleReadTimeoutStateMachine?.requestEndSent() {

Tests/AsyncHTTPClientTests/HTTPClientTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3405,4 +3405,36 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
34053405

34063406
XCTAssertNoThrow(try client.execute(request: request).wait())
34073407
}
3408+
3409+
func testCancelingHTTP1RequestAfterHeaderSend() throws {
3410+
var request = try HTTPClient.Request(url: self.defaultHTTPBin.baseURL + "/wait", method: .POST)
3411+
// non-empty body is important
3412+
request.body = .byteBuffer(ByteBuffer([1]))
3413+
3414+
class CancelAfterHeadSend: HTTPClientResponseDelegate {
3415+
init() {}
3416+
func didFinishRequest(task: AsyncHTTPClient.HTTPClient.Task<Void>) throws -> Void { }
3417+
func didSendRequestHead(task: HTTPClient.Task<Void>, _ head: HTTPRequestHead) {
3418+
task.cancel()
3419+
}
3420+
}
3421+
XCTAssertThrowsError(try defaultClient.execute(request: request, delegate: CancelAfterHeadSend()).wait())
3422+
}
3423+
3424+
func testCancelingHTTP2RequestAfterHeaderSend() throws {
3425+
let bin = HTTPBin(.http2())
3426+
defer { XCTAssertNoThrow(try bin.shutdown()) }
3427+
var request = try HTTPClient.Request(url: bin.baseURL + "/wait", method: .POST)
3428+
// non-empty body is important
3429+
request.body = .byteBuffer(ByteBuffer([1]))
3430+
3431+
class CancelAfterHeadSend: HTTPClientResponseDelegate {
3432+
init() {}
3433+
func didFinishRequest(task: AsyncHTTPClient.HTTPClient.Task<Void>) throws -> Void { }
3434+
func didSendRequestHead(task: HTTPClient.Task<Void>, _ head: HTTPRequestHead) {
3435+
task.cancel()
3436+
}
3437+
}
3438+
XCTAssertThrowsError(try defaultClient.execute(request: request, delegate: CancelAfterHeadSend()).wait())
3439+
}
34083440
}

0 commit comments

Comments
 (0)