Skip to content

Commit f7a84af

Browse files
authored
Fix request hang if delegate fails promise returned by didReceiveBodyPart (#633)
* Add test which currently hangs indefinitely * Fix request hang if delegate fails promise * Run `generate_linux_tests.rb` and `SwiftFormat`
1 parent 4d69c84 commit f7a84af

File tree

3 files changed

+74
-18
lines changed

3 files changed

+74
-18
lines changed

Sources/AsyncHTTPClient/RequestBag.swift

+6-18
Original file line numberDiff line numberDiff line change
@@ -276,14 +276,7 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
276276
self.delegate.didReceiveBodyPart(task: self.task, buffer)
277277
.hop(to: self.task.eventLoop)
278278
.whenComplete {
279-
switch $0 {
280-
case .success:
281-
self.consumeMoreBodyData0(resultOfPreviousConsume: $0)
282-
case .failure(let error):
283-
// if in the response stream consumption an error has occurred, we need to
284-
// cancel the running request and fail the task.
285-
self.fail(error)
286-
}
279+
self.consumeMoreBodyData0(resultOfPreviousConsume: $0)
287280
}
288281

289282
case .succeedRequest:
@@ -325,18 +318,13 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
325318
self.delegate.didReceiveBodyPart(task: self.task, byteBuffer)
326319
.hop(to: self.task.eventLoop)
327320
.whenComplete { result in
328-
switch result {
329-
case .success:
330-
if self.consumeBodyPartStackDepth < Self.maxConsumeBodyPartStackDepth {
321+
if self.consumeBodyPartStackDepth < Self.maxConsumeBodyPartStackDepth {
322+
self.consumeMoreBodyData0(resultOfPreviousConsume: result)
323+
} else {
324+
// We need to unwind the stack, let's take a break.
325+
self.task.eventLoop.execute {
331326
self.consumeMoreBodyData0(resultOfPreviousConsume: result)
332-
} else {
333-
// We need to unwind the stack, let's take a break.
334-
self.task.eventLoop.execute {
335-
self.consumeMoreBodyData0(resultOfPreviousConsume: result)
336-
}
337327
}
338-
case .failure(let error):
339-
self.fail(error)
340328
}
341329
}
342330

Tests/AsyncHTTPClientTests/RequestBagTests+XCTest.swift

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ extension RequestBagTests {
3434
("testCancelFailsTaskWhenTaskIsQueued", testCancelFailsTaskWhenTaskIsQueued),
3535
("testFailsTaskWhenTaskIsWaitingForMoreFromServer", testFailsTaskWhenTaskIsWaitingForMoreFromServer),
3636
("testChannelBecomingWritableDoesntCrashCancelledTask", testChannelBecomingWritableDoesntCrashCancelledTask),
37+
("testDidReceiveBodyPartFailedPromise", testDidReceiveBodyPartFailedPromise),
3738
("testHTTPUploadIsCancelledEvenThoughRequestSucceeds", testHTTPUploadIsCancelledEvenThoughRequestSucceeds),
3839
("testRaceBetweenConnectionCloseAndDemandMoreData", testRaceBetweenConnectionCloseAndDemandMoreData),
3940
("testRedirectWith3KBBody", testRedirectWith3KBBody),

Tests/AsyncHTTPClientTests/RequestBagTests.swift

+67
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,73 @@ final class RequestBagTests: XCTestCase {
455455
}
456456
}
457457

458+
func testDidReceiveBodyPartFailedPromise() {
459+
let embeddedEventLoop = EmbeddedEventLoop()
460+
defer { XCTAssertNoThrow(try embeddedEventLoop.syncShutdownGracefully()) }
461+
let logger = Logger(label: "test")
462+
463+
var maybeRequest: HTTPClient.Request?
464+
465+
XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(
466+
url: "https://swift.org",
467+
method: .POST,
468+
body: .byteBuffer(.init(bytes: [1]))
469+
))
470+
guard let request = maybeRequest else { return XCTFail("Expected to have a request") }
471+
472+
struct MyError: Error, Equatable {}
473+
final class Delegate: HTTPClientResponseDelegate {
474+
typealias Response = Void
475+
let didFinishPromise: EventLoopPromise<Void>
476+
init(didFinishPromise: EventLoopPromise<Void>) {
477+
self.didFinishPromise = didFinishPromise
478+
}
479+
480+
func didReceiveBodyPart(task: HTTPClient.Task<Void>, _ buffer: ByteBuffer) -> EventLoopFuture<Void> {
481+
task.eventLoop.makeFailedFuture(MyError())
482+
}
483+
484+
func didReceiveError(task: HTTPClient.Task<Void>, _ error: Error) {
485+
self.didFinishPromise.fail(error)
486+
}
487+
488+
func didFinishRequest(task: AsyncHTTPClient.HTTPClient.Task<Void>) throws {
489+
XCTFail("\(#function) should not be called")
490+
self.didFinishPromise.succeed(())
491+
}
492+
}
493+
let delegate = Delegate(didFinishPromise: embeddedEventLoop.makePromise())
494+
var maybeRequestBag: RequestBag<Delegate>?
495+
XCTAssertNoThrow(maybeRequestBag = try RequestBag(
496+
request: request,
497+
eventLoopPreference: .delegate(on: embeddedEventLoop),
498+
task: .init(eventLoop: embeddedEventLoop, logger: logger),
499+
redirectHandler: nil,
500+
connectionDeadline: .now() + .seconds(30),
501+
requestOptions: .forTests(),
502+
delegate: delegate
503+
))
504+
guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") }
505+
506+
let executor = MockRequestExecutor(eventLoop: embeddedEventLoop)
507+
508+
executor.runRequest(bag)
509+
510+
bag.resumeRequestBodyStream()
511+
XCTAssertNoThrow(try executor.receiveRequestBody { XCTAssertEqual($0, ByteBuffer(bytes: [1])) })
512+
513+
bag.receiveResponseHead(.init(version: .http1_1, status: .ok))
514+
515+
bag.succeedRequest([ByteBuffer([1])])
516+
517+
XCTAssertThrowsError(try delegate.didFinishPromise.futureResult.wait()) { error in
518+
XCTAssertEqualTypeAndValue(error, MyError())
519+
}
520+
XCTAssertThrowsError(try bag.task.futureResult.wait()) { error in
521+
XCTAssertEqualTypeAndValue(error, MyError())
522+
}
523+
}
524+
458525
func testHTTPUploadIsCancelledEvenThoughRequestSucceeds() {
459526
let embeddedEventLoop = EmbeddedEventLoop()
460527
defer { XCTAssertNoThrow(try embeddedEventLoop.syncShutdownGracefully()) }

0 commit comments

Comments
 (0)