diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift index 3eb18ef35..d825171ea 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift @@ -71,7 +71,7 @@ final class HTTPConnectionPool { self._state = StateMachine( eventLoopGroup: eventLoopGroup, idGenerator: idGenerator, - maximumConcurrentHTTP1Connections: 8 + maximumConcurrentHTTP1Connections: clientConfiguration.connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit ) } diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 3f81e5b74..0edac6b16 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -849,11 +849,21 @@ extension HTTPClient.Configuration { /// Connection pool configuration. public struct ConnectionPool: Hashable { - // Specifies amount of time connections are kept idle in the pool. + /// Specifies amount of time connections are kept idle in the pool. After this time has passed without a new + /// request the connections are closed. public var idleTimeout: TimeAmount + /// The maximum number of connections that are kept alive in the connection pool per host. If requests with + /// an explicit eventLoopRequirement are sent, this number might be exceeded due to overflow connections. + public var concurrentHTTP1ConnectionsPerHostSoftLimit: Int + public init(idleTimeout: TimeAmount = .seconds(60)) { + self.init(idleTimeout: idleTimeout, concurrentHTTP1ConnectionsPerHostSoftLimit: 8) + } + + public init(idleTimeout: TimeAmount, concurrentHTTP1ConnectionsPerHostSoftLimit: Int) { self.idleTimeout = idleTimeout + self.concurrentHTTP1ConnectionsPerHostSoftLimit = concurrentHTTP1ConnectionsPerHostSoftLimit } } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index 98bfb0b54..2ab8856bf 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -135,6 +135,7 @@ extension HTTPClientTests { ("testCloseWhileBackpressureIsExertedIsFine", testCloseWhileBackpressureIsExertedIsFine), ("testErrorAfterCloseWhileBackpressureExerted", testErrorAfterCloseWhileBackpressureExerted), ("testRequestSpecificTLS", testRequestSpecificTLS), + ("testConnectionPoolSizeConfigValueIsRespected", testConnectionPoolSizeConfigValueIsRespected), ] } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 5cdc2ada1..3311ffba7 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -3060,4 +3060,47 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(firstConnectionNumber, secondConnectionNumber, "Identical TLS configurations did not use the same connection") XCTAssertNotEqual(thirdConnectionNumber, firstConnectionNumber, "Different TLS configurations did not use different connections.") } + + func testConnectionPoolSizeConfigValueIsRespected() { + let numberOfRequestsPerThread = 1000 + let numberOfParallelWorkers = 16 + let poolSize = 12 + + let httpBin = HTTPBin() + defer { XCTAssertNoThrow(try httpBin.shutdown()) } + + let group = MultiThreadedEventLoopGroup(numberOfThreads: 4) + defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } + + let configuration = HTTPClient.Configuration( + connectionPool: .init( + idleTimeout: .seconds(30), + concurrentHTTP1ConnectionsPerHostSoftLimit: poolSize + ) + ) + let client = HTTPClient(eventLoopGroupProvider: .shared(group), configuration: configuration) + defer { XCTAssertNoThrow(try client.syncShutdown()) } + + let g = DispatchGroup() + for workerID in 0..