Skip to content

Commit 0469acb

Browse files
authored
Tollerate more data after request body is cancelled (#617)
* Tollerate more data after request body is cancelled * wait is not needed if we shutdown the server first * Remove test that depends on external resources * Remove unused conformance to Equatable * SwiftFormat * run generate_linux_tests.rb * Increase timeout for CI
1 parent e294c8f commit 0469acb

File tree

3 files changed

+32
-7
lines changed

3 files changed

+32
-7
lines changed

Sources/AsyncHTTPClient/AsyncAwait/Transaction+StateMachine.swift

+11-7
Original file line numberDiff line numberDiff line change
@@ -429,17 +429,20 @@ extension Transaction {
429429
case .executing(_, _, .waitingForResponseHead):
430430
preconditionFailure("If we receive a response body, we must have received a head before")
431431

432-
case .executing(let context, let requestState, .buffering(let streamID, var currentBuffer, next: let next)):
433-
guard case .askExecutorForMore = next else {
434-
preconditionFailure("If we have received an error or eof before, why did we get another body part? Next: \(next)")
435-
}
432+
case .executing(_, _, .buffering(_, _, next: .endOfFile)):
433+
preconditionFailure("If we have received an eof before, why did we get another body part?")
436434

435+
case .executing(_, _, .buffering(_, _, next: .error)):
436+
// we might still get pending buffers if the user has canceled the request
437+
return .none
438+
439+
case .executing(let context, let requestState, .buffering(let streamID, var currentBuffer, next: .askExecutorForMore)):
437440
if currentBuffer.isEmpty {
438441
currentBuffer = buffer
439442
} else {
440443
currentBuffer.append(contentsOf: buffer)
441444
}
442-
self.state = .executing(context, requestState, .buffering(streamID, currentBuffer, next: next))
445+
self.state = .executing(context, requestState, .buffering(streamID, currentBuffer, next: .askExecutorForMore))
443446
return .none
444447

445448
case .executing(let executor, let requestState, .waitingForResponseIterator(var currentBuffer, next: let next)):
@@ -690,10 +693,11 @@ extension Transaction {
690693
case .finished:
691694
// the request failed or was cancelled before, we can ignore all events
692695
return .none
693-
696+
case .executing(_, _, .buffering(_, _, next: .error)):
697+
// we might still get pending buffers if the user has canceled the request
698+
return .none
694699
case .executing(_, _, .waitingForResponseIterator(_, next: .error)),
695700
.executing(_, _, .waitingForResponseIterator(_, next: .endOfFile)),
696-
.executing(_, _, .buffering(_, _, next: .error)),
697701
.executing(_, _, .buffering(_, _, next: .endOfFile)),
698702
.executing(_, _, .finished(_, _)):
699703
preconditionFailure("Already received an eof or error before. Must not receive further events. Invalid state: \(self.state)")

Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests+XCTest.swift

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ extension AsyncAwaitEndToEndTests {
4343
("testInvalidURL", testInvalidURL),
4444
("testRedirectChangesHostHeader", testRedirectChangesHostHeader),
4545
("testShutdown", testShutdown),
46+
("testCancelingBodyDoesNotCrash", testCancelingBodyDoesNotCrash),
4647
]
4748
}
4849
}

Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift

+20
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,26 @@ final class AsyncAwaitEndToEndTests: XCTestCase {
578578
}
579579
#endif
580580
}
581+
582+
/// Regression test for https://github.com/swift-server/async-http-client/issues/612
583+
func testCancelingBodyDoesNotCrash() {
584+
#if compiler(>=5.5.2) && canImport(_Concurrency)
585+
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
586+
XCTAsyncTest {
587+
let client = makeDefaultHTTPClient()
588+
defer { XCTAssertNoThrow(try client.syncShutdown()) }
589+
let bin = HTTPBin(.http2(compress: true))
590+
defer { XCTAssertNoThrow(try bin.shutdown()) }
591+
592+
let request = HTTPClientRequest(url: "https://127.0.0.1:\(bin.port)/mega-chunked")
593+
let response = try await client.execute(request, deadline: .now() + .seconds(10))
594+
595+
await XCTAssertThrowsError(try await response.body.collect(upTo: 100)) { error in
596+
XCTAssert(error is NIOTooManyBytesError)
597+
}
598+
}
599+
#endif
600+
}
581601
}
582602

583603
#if compiler(>=5.5.2) && canImport(_Concurrency)

0 commit comments

Comments
 (0)