From 9f76fd71c0ee9c6af48f523ae2b71ab1be6fbaf7 Mon Sep 17 00:00:00 2001 From: adtrevor Date: Fri, 13 Mar 2020 06:48:33 +0100 Subject: [PATCH] Add testProxyStreamingNoDeadlock test Because of there is a maximum number of concurrent connections per host it is possible for the client to deadlock itself when making requests with a streamed body if the body data is itself requested from the same host. If we reach the maximum number of concurrent requests for a certain host, and if all active tasks use a streamed body that obtains its data from requests to the same host, then the bodies will never be obtained as the requests they need to make will be put on a queue whose ability to dequeue them depends on their very result -> deadlock. --- .../HTTPClientInternalTests+XCTest.swift | 1 + .../HTTPClientInternalTests.swift | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift index 870ddfc37..a95b21304 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift @@ -29,6 +29,7 @@ extension HTTPClientInternalTests { ("testBadHTTPRequest", testBadHTTPRequest), ("testHTTPPartsHandlerMultiBody", testHTTPPartsHandlerMultiBody), ("testProxyStreaming", testProxyStreaming), + ("testProxyStreamingNoDeadlock", testProxyStreamingNoDeadlock), ("testProxyStreamingFailure", testProxyStreamingFailure), ("testUploadStreamingBackpressure", testUploadStreamingBackpressure), ("testRequestURITrailingSlash", testRequestURITrailingSlash), diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift index 9288814c6..58f92b529 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift @@ -133,6 +133,40 @@ class HTTPClientInternalTests: XCTestCase { XCTAssertEqual("id: 0id: 1id: 2id: 3id: 4id: 5id: 6id: 7id: 8id: 9", data.data) } + func testProxyStreamingNoDeadlock() throws { + let httpBin = HTTPBin() + let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + defer { + XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) + XCTAssertNoThrow(try httpBin.shutdown()) + } + + let goSignalPromise = httpClient.eventLoopGroup.next().makePromise(of: Void.self) + let goSignal = goSignalPromise.futureResult + + let body: HTTPClient.Body = .stream(length: 50) { writer in + goSignal.flatMap { + httpClient.get(url: "http://localhost/(httpBin.port)/get") + }.flatMap { _ in + writer.write(IOData.byteBuffer(.of(bytes: .init(repeating: 0, count: 50)))) + } + } + + var allRequests = [EventLoopFuture]() + + // Make sure to exceed maximum number of concurrent connections + for _ in 1...50 { + allRequests.append(httpClient.post(url: "http://localhost/(httpBin.port)/post", body: body)) + } + + // Now allow requests to actually send their body data + goSignalPromise.succeed(()) + + let everythingSucceeded = EventLoopFuture.andAllSucceed(allRequests, on: httpClient.eventLoopGroup.next()) + + XCTAssertNoThrow(try everythingSucceeded.timeout(after: .seconds(5)).wait()) + } + func testProxyStreamingFailure() throws { let httpBin = HTTPBin() let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)