From ed6f11561b0928b7b8de53ab7eb3d14756d19cf3 Mon Sep 17 00:00:00 2001 From: Fabian Fett <fabianfett@apple.com> Date: Mon, 13 Sep 2021 11:58:42 +0200 Subject: [PATCH] Ensure the ConnectionPool calls shutdown on active connections. --- .../ConnectionPool/HTTPConnectionPool.swift | 2 +- .../HTTPConnectionPoolTests+XCTest.swift | 1 + .../HTTPConnectionPoolTests.swift | 56 ++++++++++++++++++- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift index 475c3b8f3..c4759a6c0 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift @@ -235,7 +235,7 @@ final class HTTPConnectionPool { } for connection in cleanupContext.cancel { - connection.close(promise: nil) + connection.shutdown() } for connectionID in cleanupContext.connectBackoff { diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests+XCTest.swift index da3bb5db7..dd278a0dc 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests+XCTest.swift @@ -31,6 +31,7 @@ extension HTTPConnectionPoolTests { ("testConnectionCreationIsRetriedUntilRequestIsFailed", testConnectionCreationIsRetriedUntilRequestIsFailed), ("testConnectionCreationIsRetriedUntilPoolIsShutdown", testConnectionCreationIsRetriedUntilPoolIsShutdown), ("testConnectionCreationIsRetriedUntilRequestIsCancelled", testConnectionCreationIsRetriedUntilRequestIsCancelled), + ("testConnectionShutdownIsCalledOnActiveConnections", testConnectionShutdownIsCalledOnActiveConnections), ] } } diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift index dd13284b1..019e1d414 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift @@ -294,7 +294,7 @@ class HTTPConnectionPoolTests: XCTestCase { let eventLoop = eventLoopGroup.next() defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - let request = try! HTTPClient.Request(url: "http://localhost:9000") + let request = try! HTTPClient.Request(url: "http://localhost:\(httpBin.port)") let poolDelegate = TestDelegate(eventLoop: eventLoop) let pool = HTTPConnectionPool( @@ -318,7 +318,7 @@ class HTTPConnectionPoolTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag<ResponseAccumulator>? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://localhost:\(httpBin.port)")) + XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)")) XCTAssertNoThrow(maybeRequestBag = try RequestBag( request: XCTUnwrap(maybeRequest), eventLoopPreference: .indifferent, @@ -332,13 +332,63 @@ class HTTPConnectionPoolTests: XCTestCase { guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } pool.executeRequest(requestBag) - XCTAssertNoThrow(try eventLoop.scheduleTask(in: .milliseconds(100)) {}.futureResult.wait()) + XCTAssertNoThrow(try eventLoop.scheduleTask(in: .seconds(1)) {}.futureResult.wait()) requestBag.cancel() XCTAssertThrowsError(try requestBag.task.futureResult.wait()) { XCTAssertEqual($0 as? HTTPClientError, .cancelled) } + XCTAssertGreaterThanOrEqual(httpBin.createdConnections, 3) + } + + func testConnectionShutdownIsCalledOnActiveConnections() { + let httpBin = HTTPBin() + defer { XCTAssertNoThrow(try httpBin.shutdown()) } + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let eventLoop = eventLoopGroup.next() + defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } + + let request = try! HTTPClient.Request(url: "http://localhost:\(httpBin.port)") + let poolDelegate = TestDelegate(eventLoop: eventLoop) + + let pool = HTTPConnectionPool( + eventLoopGroup: eventLoopGroup, + sslContextCache: .init(), + tlsConfiguration: .none, + clientConfiguration: .init(), + key: .init(request), + delegate: poolDelegate, + idGenerator: .init(), + backgroundActivityLogger: .init(label: "test") + ) + + var maybeRequest: HTTPClient.Request? + var maybeRequestBag: RequestBag<ResponseAccumulator>? + XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/wait")) + XCTAssertNoThrow(maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(5), + idleReadTimeout: nil, + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + )) + + guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } + + pool.executeRequest(requestBag) + XCTAssertNoThrow(try eventLoop.scheduleTask(in: .milliseconds(500)) {}.futureResult.wait()) + pool.shutdown() + + XCTAssertNoThrow(try poolDelegate.future.wait()) + + XCTAssertThrowsError(try requestBag.task.futureResult.wait()) { + XCTAssertEqual($0 as? HTTPClientError, .cancelled) + } + XCTAssertGreaterThanOrEqual(httpBin.createdConnections, 1) + XCTAssertGreaterThanOrEqual(httpBin.activeConnections, 0) } }