Files
async-http-client/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP1ConnectionsTest.swift
T
1290119b31 Assume http2 connection by default, instead of http1 (#758)
Since most of the servers now conform to http2, the change here updates
the behaviour of assuming the connection to be http2 and not http1 by
default. It will migrate to http1 if the server only supports http1.
One can set the `httpVersion` in `ClientConfiguration` to `.http1Only`
which will start with http1 instead of http2.

Additional Changes:

- Fixed an off by one error in the maximum additional general purpose
connection check
- Updated tests

---------

Co-authored-by: Ayush Garg <ayushgarg@apple.com>
Co-authored-by: David Nadoba <d_nadoba@apple.com>
Co-authored-by: Fabian Fett <fabianfett@apple.com>
2024-08-15 14:26:52 +02:00

634 lines
28 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the AsyncHTTPClient open source project
//
// Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
@testable import AsyncHTTPClient
import NIOCore
import NIOEmbedded
import XCTest
class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase {
func testCreatingConnections() {
let elg = EmbeddedEventLoopGroup(loops: 4)
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil)
let el1 = elg.next()
let el2 = elg.next()
// general purpose connection
XCTAssertEqual(connections.startingGeneralPurposeConnections, 0)
XCTAssertEqual(connections.startingEventLoopConnections(on: el1), 0)
let conn1ID = connections.createNewConnection(on: el1)
XCTAssertEqual(connections.startingGeneralPurposeConnections, 1)
XCTAssertEqual(connections.startingEventLoopConnections(on: el1), 0)
let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1)
let (conn1Index, conn1CreatedContext) = connections.newHTTP1ConnectionEstablished(conn1)
XCTAssertEqual(conn1CreatedContext.use, .generalPurpose)
XCTAssert(conn1CreatedContext.eventLoop === el1)
XCTAssertEqual(connections.leaseConnection(at: conn1Index), conn1)
XCTAssertEqual(connections.startingGeneralPurposeConnections, 0)
// eventLoop connection
let conn2ID = connections.createNewOverflowConnection(on: el2)
XCTAssertEqual(connections.startingGeneralPurposeConnections, 0)
XCTAssertEqual(connections.startingEventLoopConnections(on: el2), 1)
let conn2: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn2ID, eventLoop: el2)
let (conn2Index, conn2CreatedContext) = connections.newHTTP1ConnectionEstablished(conn2)
XCTAssertEqual(conn2CreatedContext.use, .eventLoop(el2))
XCTAssert(conn2CreatedContext.eventLoop === el2)
XCTAssertEqual(connections.leaseConnection(at: conn2Index), conn2)
XCTAssertEqual(connections.startingEventLoopConnections(on: el2), 0)
}
func testCreatingConnectionAndFailing() {
let elg = EmbeddedEventLoopGroup(loops: 4)
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil)
let el1 = elg.next()
let el2 = elg.next()
// general purpose connection
XCTAssertEqual(connections.startingGeneralPurposeConnections, 0)
XCTAssertEqual(connections.startingEventLoopConnections(on: el1), 0)
let conn1ID = connections.createNewConnection(on: el1)
XCTAssertEqual(conn1ID, 0)
XCTAssertEqual(connections.startingGeneralPurposeConnections, 1)
XCTAssertEqual(connections.startingEventLoopConnections(on: el1), 0)
// connection failed to start. 1. backoff
let backoff1EL = connections.backoffNextConnectionAttempt(conn1ID)
XCTAssert(backoff1EL === el1)
// backoff done. 2. decide what's next
guard let (conn1FailIndex, conn1FailContext) = connections.failConnection(conn1ID) else {
return XCTFail("Expected that the connection is remembered")
}
XCTAssert(conn1FailContext.eventLoop === el1)
XCTAssertEqual(conn1FailContext.use, .generalPurpose)
XCTAssertEqual(conn1FailContext.connectionsStartingForUseCase, 0)
let (replaceConn1ID, replaceConn1EL) = connections.replaceConnection(at: conn1FailIndex)
XCTAssert(replaceConn1EL === el1)
XCTAssertEqual(replaceConn1ID, 1)
// eventLoop connection
let conn2ID = connections.createNewOverflowConnection(on: el2)
// the replacement connection is starting
XCTAssertEqual(connections.startingGeneralPurposeConnections, 1)
XCTAssertEqual(connections.startingEventLoopConnections(on: el2), 1)
let backoff2EL = connections.backoffNextConnectionAttempt(conn2ID)
XCTAssert(backoff2EL === el2)
guard let (conn2FailIndex, conn2FailContext) = connections.failConnection(conn2ID) else {
return XCTFail("Expected that the connection is remembered")
}
XCTAssert(conn2FailContext.eventLoop === el2)
XCTAssertEqual(conn2FailContext.use, .eventLoop(el2))
XCTAssertEqual(conn2FailContext.connectionsStartingForUseCase, 0)
connections.removeConnection(at: conn2FailIndex)
// the replacement connection is still starting
XCTAssertEqual(connections.startingGeneralPurposeConnections, 1)
}
func testLeaseConnectionOnPreferredAndAvailableEL() {
let elg = EmbeddedEventLoopGroup(loops: 4)
let el1 = elg.next()
let el2 = elg.next()
let el3 = elg.next()
let el4 = elg.next()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil)
for el in [el1, el2, el3, el4] {
XCTAssertEqual(connections.startingGeneralPurposeConnections, 0)
XCTAssertEqual(connections.startingEventLoopConnections(on: el), 0)
let connID = connections.createNewConnection(on: el)
XCTAssertEqual(connections.startingGeneralPurposeConnections, 1)
XCTAssertEqual(connections.startingEventLoopConnections(on: el), 0)
let conn: HTTPConnectionPool.Connection = .__testOnly_connection(id: connID, eventLoop: el)
let (_, connCreatedContext) = connections.newHTTP1ConnectionEstablished(conn)
XCTAssertEqual(connCreatedContext.use, .generalPurpose)
XCTAssert(connCreatedContext.eventLoop === el)
XCTAssertEqual(connections.startingGeneralPurposeConnections, 0)
}
let connection = connections.leaseConnection(onPreferred: el1)
XCTAssertEqual(connection, .__testOnly_connection(id: 0, eventLoop: el1))
}
func testLeaseConnectionOnPreferredButUnavailableEL() {
let elg = EmbeddedEventLoopGroup(loops: 5)
let el1 = elg.next()
let el2 = elg.next()
let el3 = elg.next()
let el4 = elg.next()
let el5 = elg.next()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil)
for el in [el1, el2, el3, el4] {
XCTAssertEqual(connections.startingGeneralPurposeConnections, 0)
XCTAssertEqual(connections.startingEventLoopConnections(on: el), 0)
let connID = connections.createNewConnection(on: el)
XCTAssertEqual(connections.startingGeneralPurposeConnections, 1)
XCTAssertEqual(connections.startingEventLoopConnections(on: el), 0)
let conn: HTTPConnectionPool.Connection = .__testOnly_connection(id: connID, eventLoop: el)
let (_, connCreatedContext) = connections.newHTTP1ConnectionEstablished(conn)
XCTAssertEqual(connCreatedContext.use, .generalPurpose)
XCTAssert(connCreatedContext.eventLoop === el)
XCTAssertEqual(connections.startingGeneralPurposeConnections, 0)
}
let connection = connections.leaseConnection(onPreferred: el5)
XCTAssertEqual(connection, .__testOnly_connection(id: 3, eventLoop: el4))
}
func testLeaseConnectionOnRequiredButUnavailableEL() {
let elg = EmbeddedEventLoopGroup(loops: 5)
let el1 = elg.next()
let el2 = elg.next()
let el3 = elg.next()
let el4 = elg.next()
let el5 = elg.next()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil)
for el in [el1, el2, el3, el4] {
XCTAssertEqual(connections.startingGeneralPurposeConnections, 0)
XCTAssertEqual(connections.startingEventLoopConnections(on: el), 0)
let connID = connections.createNewConnection(on: el)
XCTAssertEqual(connections.startingGeneralPurposeConnections, 1)
XCTAssertEqual(connections.startingEventLoopConnections(on: el), 0)
let conn: HTTPConnectionPool.Connection = .__testOnly_connection(id: connID, eventLoop: el)
let (_, connCreatedContext) = connections.newHTTP1ConnectionEstablished(conn)
XCTAssertEqual(connCreatedContext.use, .generalPurpose)
XCTAssert(connCreatedContext.eventLoop === el)
XCTAssertEqual(connections.startingGeneralPurposeConnections, 0)
}
let connection = connections.leaseConnection(onRequired: el5)
XCTAssertEqual(connection, .none)
}
func testLeaseConnectionOnRequiredAndAvailableEL() {
let elg = EmbeddedEventLoopGroup(loops: 2)
let el1 = elg.next()
let el2 = elg.next()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil)
for el in [el1, el1, el1, el1, el2] {
let connID = connections.createNewConnection(on: el)
let conn: HTTPConnectionPool.Connection = .__testOnly_connection(id: connID, eventLoop: el)
_ = connections.newHTTP1ConnectionEstablished(conn)
}
// 1. get el from general pool, even though el is required!
guard let lease1 = connections.leaseConnection(onRequired: el1) else {
return XCTFail("Expected to get a connection at this point.")
}
// the last created connection on the correct el is the shortest amount idle. we should use this
XCTAssertEqual(lease1, .__testOnly_connection(id: 3, eventLoop: el1))
_ = connections.releaseConnection(lease1.id)
// 2. create specialized el connection
let connID5 = connections.createNewOverflowConnection(on: el1)
XCTAssertEqual(connections.startingEventLoopConnections(on: el1), 1)
let conn5: HTTPConnectionPool.Connection = .__testOnly_connection(id: connID5, eventLoop: el1)
_ = connections.newHTTP1ConnectionEstablished(conn5)
XCTAssertEqual(connections.startingEventLoopConnections(on: el1), 0)
// 3. get el from specialized pool, since it is the newest!
guard let lease2 = connections.leaseConnection(onRequired: el1) else {
return XCTFail("Expected to get a connection at this point.")
}
XCTAssertEqual(lease2, conn5)
_ = connections.releaseConnection(lease2.id)
// 4. create another general purpose connection on the correct el
let connID6 = connections.createNewConnection(on: el1)
let conn6: HTTPConnectionPool.Connection = .__testOnly_connection(id: connID6, eventLoop: el1)
_ = connections.newHTTP1ConnectionEstablished(conn6)
// 5. get el from general pool, since it is the newest!
guard let lease3 = connections.leaseConnection(onRequired: el1) else {
return XCTFail("Expected to get a connection at this point.")
}
// the last created connection is the shortest amount idle. we should use this
XCTAssertEqual(lease3, conn6)
_ = connections.releaseConnection(lease3.id)
}
func testCloseConnectionIfIdle() {
let elg = EmbeddedEventLoopGroup(loops: 1)
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil)
let el1 = elg.next()
// connection is idle
let conn1ID = connections.createNewConnection(on: el1)
let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1)
_ = connections.newHTTP1ConnectionEstablished(conn1)
XCTAssertEqual(connections.closeConnectionIfIdle(conn1ID), conn1)
// connection is not idle
let conn2ID = connections.createNewConnection(on: el1)
let conn2: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn2ID, eventLoop: el1)
let (conn2Index, _) = connections.newHTTP1ConnectionEstablished(conn2)
XCTAssertEqual(connections.leaseConnection(at: conn2Index), conn2)
XCTAssertNil(connections.closeConnectionIfIdle(conn2ID))
}
func testCloseConnectionIfIdleButLeasedRaceCondition() {
let elg = EmbeddedEventLoopGroup(loops: 1)
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil)
let el1 = elg.next()
// connection is idle
let connID = connections.createNewConnection(on: el1)
let conn: HTTPConnectionPool.Connection = .__testOnly_connection(id: connID, eventLoop: el1)
_ = connections.newHTTP1ConnectionEstablished(conn)
// connection is leased
let lease = connections.leaseConnection(onPreferred: el1)
XCTAssertEqual(lease, conn)
// timeout arrives minimal to late
XCTAssertEqual(connections.closeConnectionIfIdle(connID), nil)
}
func testCloseConnectionIfIdleButClosedRaceCondition() {
let elg = EmbeddedEventLoopGroup(loops: 1)
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil)
let el1 = elg.next()
// connection is idle
let connID = connections.createNewConnection(on: el1)
let conn: HTTPConnectionPool.Connection = .__testOnly_connection(id: connID, eventLoop: el1)
_ = connections.newHTTP1ConnectionEstablished(conn)
_ = connections.failConnection(connID)
// timeout arrives minimal to late
XCTAssertEqual(connections.closeConnectionIfIdle(connID), nil)
}
func testShutdown() {
let elg = EmbeddedEventLoopGroup(loops: 4)
let el1 = elg.next()
let el2 = elg.next()
let el3 = elg.next()
let el4 = elg.next()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil)
for el in [el1, el2, el3, el4] {
let connID = connections.createNewConnection(on: el)
let conn: HTTPConnectionPool.Connection = .__testOnly_connection(id: connID, eventLoop: el)
let (_, connContext) = connections.newHTTP1ConnectionEstablished(conn)
XCTAssertEqual(connContext.use, .generalPurpose)
XCTAssert(connContext.eventLoop === el)
}
XCTAssertEqual(connections.stats.backingOff, 0)
XCTAssertEqual(connections.stats.leased, 0)
XCTAssertEqual(connections.stats.idle, 4)
// connection is leased
guard let lease = connections.leaseConnection(onPreferred: el1) else {
return XCTFail("Expected to be able to lease a connection")
}
XCTAssertEqual(lease, .__testOnly_connection(id: 0, eventLoop: el1))
XCTAssertEqual(connections.stats.leased, 1)
XCTAssertEqual(connections.stats.idle, 3)
// start another connection that fails
let backingOffID = connections.createNewConnection(on: el1)
XCTAssert(connections.backoffNextConnectionAttempt(backingOffID) === el1)
// start another connection
let startingID = connections.createNewConnection(on: el2)
let context = connections.shutdown()
XCTAssertEqual(context.close.count, 3)
XCTAssertEqual(context.cancel, [lease])
XCTAssertEqual(context.connectBackoff, [backingOffID])
XCTAssertEqual(connections.stats.idle, 0)
XCTAssertEqual(connections.stats.backingOff, 0)
XCTAssertEqual(connections.stats.leased, 1)
XCTAssertEqual(connections.stats.connecting, 1)
XCTAssertFalse(connections.isEmpty)
let (releaseIndex, _) = connections.releaseConnection(lease.id)
XCTAssertEqual(connections.closeConnection(at: releaseIndex), lease)
XCTAssertFalse(connections.isEmpty)
guard let (failIndex, _) = connections.failConnection(startingID) else {
return XCTFail("Expected that the connection is remembered")
}
connections.removeConnection(at: failIndex)
XCTAssertTrue(connections.isEmpty)
}
func testMigrationFromHTTP2() {
let elg = EmbeddedEventLoopGroup(loops: 4)
let generator = HTTPConnectionPool.Connection.ID.Generator()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil)
let el1 = elg.next()
let el2 = elg.next()
let conn1ID = generator.next()
let conn2ID = generator.next()
connections.migrateFromHTTP2(
starting: [(conn1ID, el1)],
backingOff: [(conn2ID, el2)]
)
let newConnections = connections.createConnectionsAfterMigrationIfNeeded(
requiredEventLoopOfPendingRequests: [],
generalPurposeRequestCountGroupedByPreferredEventLoop: [(el1, 1), (el2, 1)]
)
XCTAssertTrue(newConnections.isEmpty)
let stats = connections.stats
XCTAssertEqual(stats.idle, 0)
XCTAssertEqual(stats.leased, 0)
XCTAssertEqual(stats.connecting, 1)
XCTAssertEqual(stats.backingOff, 1)
}
func testMigrationFromHTTP2WithPendingRequestsWithRequiredEventLoop() {
let elg = EmbeddedEventLoopGroup(loops: 4)
let generator = HTTPConnectionPool.Connection.ID.Generator()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil)
let el1 = elg.next()
let el2 = elg.next()
let el3 = elg.next()
let conn1ID = generator.next()
let conn2ID = generator.next()
connections.migrateFromHTTP2(
starting: [(conn1ID, el1)],
backingOff: [(conn2ID, el2)]
)
let newConnections = connections.createConnectionsAfterMigrationIfNeeded(
requiredEventLoopOfPendingRequests: [(el3, 1)],
generalPurposeRequestCountGroupedByPreferredEventLoop: []
)
XCTAssertEqual(newConnections.count, 1)
XCTAssertEqual(newConnections.first?.1.id, el3.id)
guard let conn3ID = newConnections.first?.0 else {
return XCTFail("expected to start a new connection")
}
let stats = connections.stats
XCTAssertEqual(stats.idle, 0)
XCTAssertEqual(stats.leased, 0)
XCTAssertEqual(stats.connecting, 2)
XCTAssertEqual(stats.backingOff, 1)
let conn3: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn3ID, eventLoop: el3)
let (_, context) = connections.newHTTP1ConnectionEstablished(conn3)
XCTAssertEqual(context.use, .eventLoop(el3))
XCTAssertTrue(context.eventLoop === el3)
}
func testMigrationFromHTTP2WithPendingRequestsWithRequiredEventLoopSameAsStartingConnections() {
let elg = EmbeddedEventLoopGroup(loops: 4)
let generator = HTTPConnectionPool.Connection.ID.Generator()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil)
let el1 = elg.next()
let el2 = elg.next()
let conn1ID = generator.next()
let conn2ID = generator.next()
connections.migrateFromHTTP2(
starting: [(conn1ID, el1)],
backingOff: [(conn2ID, el2)]
)
let stats = connections.stats
XCTAssertEqual(stats.idle, 0)
XCTAssertEqual(stats.leased, 0)
XCTAssertEqual(stats.connecting, 1)
XCTAssertEqual(stats.backingOff, 1)
let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1)
let (_, context) = connections.newHTTP1ConnectionEstablished(conn1)
XCTAssertEqual(context.use, .generalPurpose)
XCTAssertTrue(context.eventLoop === el1)
}
func testMigrationFromHTTP2WithPendingRequestsWithPreferredEventLoop() {
let elg = EmbeddedEventLoopGroup(loops: 4)
let generator = HTTPConnectionPool.Connection.ID.Generator()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil)
let el1 = elg.next()
let el2 = elg.next()
let el3 = elg.next()
let conn1ID = generator.next()
let conn2ID = generator.next()
connections.migrateFromHTTP2(
starting: [(conn1ID, el1)],
backingOff: [(conn2ID, el2)]
)
let newConnections = connections.createConnectionsAfterMigrationIfNeeded(
requiredEventLoopOfPendingRequests: [],
generalPurposeRequestCountGroupedByPreferredEventLoop: [(el3, 3)]
)
XCTAssertEqual(newConnections.count, 1)
XCTAssertEqual(newConnections.first?.1.id, el3.id)
guard let conn3ID = newConnections.first?.0 else {
return XCTFail("expected to start a new connection")
}
let stats = connections.stats
XCTAssertEqual(stats.idle, 0)
XCTAssertEqual(stats.leased, 0)
XCTAssertEqual(stats.connecting, 2)
XCTAssertEqual(stats.backingOff, 1)
let conn3: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn3ID, eventLoop: el3)
let (_, context) = connections.newHTTP1ConnectionEstablished(conn3)
XCTAssertEqual(context.use, .generalPurpose)
XCTAssertTrue(context.eventLoop === el3)
}
func testMigrationFromHTTP2WithAlreadyLeasedHTTP1Connection() {
let elg = EmbeddedEventLoopGroup(loops: 4)
let generator = HTTPConnectionPool.Connection.ID.Generator()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil)
let el1 = elg.next()
let el2 = elg.next()
let el3 = elg.next()
let conn1ID = connections.createNewConnection(on: el1)
let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1)
let (index, _) = connections.newHTTP1ConnectionEstablished(conn1)
_ = connections.leaseConnection(at: index)
let conn2ID = generator.next()
let conn3ID = generator.next()
connections.migrateFromHTTP2(
starting: [(conn2ID, el2)],
backingOff: [(conn3ID, el3)]
)
let newConnections = connections.createConnectionsAfterMigrationIfNeeded(
requiredEventLoopOfPendingRequests: [],
generalPurposeRequestCountGroupedByPreferredEventLoop: [(el3, 3)]
)
XCTAssertEqual(newConnections.count, 1)
XCTAssertEqual(newConnections.first?.1.id, el3.id)
guard let conn4ID = newConnections.first?.0 else {
return XCTFail("expected to start a new connection")
}
let stats = connections.stats
XCTAssertEqual(stats.idle, 0)
XCTAssertEqual(stats.leased, 1)
XCTAssertEqual(stats.connecting, 2)
XCTAssertEqual(stats.backingOff, 1)
let conn3: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn4ID, eventLoop: el3)
let (_, context) = connections.newHTTP1ConnectionEstablished(conn3)
XCTAssertEqual(context.use, .generalPurpose)
XCTAssertTrue(context.eventLoop === el3)
}
func testMigrationFromHTTP2WithMoreStartingConnectionsThanMaximumAllowedConccurentConnections() {
let elg = EmbeddedEventLoopGroup(loops: 4)
let generator = HTTPConnectionPool.Connection.ID.Generator()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 2, generator: generator, maximumConnectionUses: nil)
let el1 = elg.next()
let el2 = elg.next()
let el3 = elg.next()
let conn1ID = generator.next()
let conn2ID = generator.next()
let conn3ID = generator.next()
connections.migrateFromHTTP2(
starting: [(conn1ID, el1), (conn2ID, el2), (conn3ID, el3)],
backingOff: []
)
// first two connections should be added as general purpose connections
let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1)
let (_, context1) = connections.newHTTP1ConnectionEstablished(conn1)
XCTAssertEqual(context1.use, .generalPurpose)
XCTAssertTrue(context1.eventLoop === el1)
let conn2: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn2ID, eventLoop: el2)
let (_, context2) = connections.newHTTP1ConnectionEstablished(conn2)
XCTAssertEqual(context2.use, .generalPurpose)
XCTAssertTrue(context2.eventLoop === el2)
// additional connection should be added as overflow connection
let conn3: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn3ID, eventLoop: el3)
let (_, context3) = connections.newHTTP1ConnectionEstablished(conn3)
XCTAssertEqual(context3.use, .eventLoop(el3))
XCTAssertTrue(context3.eventLoop === el3)
}
func testMigrationFromHTTP2StartsEnoghOverflowConnectionsForRequiredEventLoopRequests() {
let elg = EmbeddedEventLoopGroup(loops: 4)
let generator = HTTPConnectionPool.Connection.ID.Generator()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 1, generator: generator, maximumConnectionUses: nil)
let el1 = elg.next()
let el2 = elg.next()
let el3 = elg.next()
let el4 = elg.next()
let conn1ID = generator.next()
let conn2ID = generator.next()
let conn3ID = generator.next()
connections.migrateFromHTTP2(
starting: [(conn1ID, el1), (conn2ID, el2), (conn3ID, el3)],
backingOff: []
)
let connectionsToCreate = connections.createConnectionsAfterMigrationIfNeeded(
requiredEventLoopOfPendingRequests: [(el2, 2), (el3, 1), (el4, 2)],
generalPurposeRequestCountGroupedByPreferredEventLoop: []
)
XCTAssertEqual(
connectionsToCreate.map { $0.1.id },
[el2.id, el4.id, el4.id],
"should create one connection for el2 and two for el4"
)
for (connID, el) in connectionsToCreate {
let conn: HTTPConnectionPool.Connection = .__testOnly_connection(id: connID, eventLoop: el)
let (_, context) = connections.newHTTP1ConnectionEstablished(conn)
XCTAssertEqual(context.use, .eventLoop(el))
XCTAssertTrue(context.eventLoop === el)
}
}
func testMigrationFromHTTP1ToHTTP2AndBackToHTTP1() throws {
let elg = EmbeddedEventLoopGroup(loops: 2)
defer { XCTAssertNoThrow(try elg.syncShutdownGracefully()) }
let el1 = elg.next()
let el2 = elg.next()
let generator = HTTPConnectionPool.Connection.ID.Generator()
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil)
let connID1 = connections.createNewConnection(on: el1)
let context = connections.migrateToHTTP2()
XCTAssertEqual(context, .init(
backingOff: [],
starting: [(connID1, el1)],
close: []
))
let connID2 = generator.next()
connections.migrateFromHTTP2(
starting: [(connID2, el2)],
backingOff: []
)
let conn2: HTTPConnectionPool.Connection = .__testOnly_connection(id: connID2, eventLoop: el2)
let (_, idleContext) = connections.newHTTP1ConnectionEstablished(conn2)
XCTAssertEqual(idleContext.use, .generalPurpose)
XCTAssertEqual(idleContext.eventLoop.id, el2.id)
}
}
extension HTTPConnectionPool.HTTP1Connections.HTTP1ToHTTP2MigrationContext: Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.close == rhs.close &&
lhs.starting.elementsEqual(rhs.starting, by: { $0.0 == $1.0 && $0.1 === $1.1 }) &&
lhs.backingOff.elementsEqual(rhs.backingOff, by: { $0.0 == $1.0 && $0.1 === $1.1 })
}
}