Skip to content

Commit 1290119

Browse files
ayush1794Ayush Gargdnadobafabianfett
authored
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>
1 parent 4b7a68e commit 1290119

9 files changed

+149
-40
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ let package = Package(
2222
],
2323
dependencies: [
2424
.package(url: "https://github.com/apple/swift-nio.git", from: "2.62.0"),
25-
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.22.0"),
25+
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.27.1"),
2626
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.19.0"),
2727
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.13.0"),
2828
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.19.0"),

Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ final class HTTPConnectionPool {
7272
idGenerator: idGenerator,
7373
maximumConcurrentHTTP1Connections: clientConfiguration.connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit,
7474
retryConnectionEstablishment: clientConfiguration.connectionPool.retryConnectionEstablishment,
75+
preferHTTP1: clientConfiguration.httpVersion == .http1Only,
7576
maximumConnectionUses: clientConfiguration.maximumUsesPerConnection
7677
)
7778
}

Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1Connections.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ extension HTTPConnectionPool {
307307
}
308308

309309
private var maximumAdditionalGeneralPurposeConnections: Int {
310-
self.maximumConcurrentConnections - (self.overflowIndex - 1)
310+
self.maximumConcurrentConnections - (self.overflowIndex)
311311
}
312312

313313
/// Is there at least one connection that is able to run requests
@@ -594,6 +594,7 @@ extension HTTPConnectionPool {
594594
eventLoop: eventLoop,
595595
maximumUses: self.maximumConnectionUses
596596
)
597+
597598
self.connections.insert(newConnection, at: self.overflowIndex)
598599
/// If we can grow, we mark the connection as a general purpose connection.
599600
/// Otherwise, it will be an overflow connection which is only used once for requests with a required event loop
@@ -610,6 +611,7 @@ extension HTTPConnectionPool {
610611
)
611612
// TODO: Maybe we want to add a static init for backing off connections to HTTP1ConnectionState
612613
backingOffConnection.failedToConnect()
614+
613615
self.connections.insert(backingOffConnection, at: self.overflowIndex)
614616
/// If we can grow, we mark the connection as a general purpose connection.
615617
/// Otherwise, it will be an overflow connection which is only used once for requests with a required event loop
@@ -637,7 +639,7 @@ extension HTTPConnectionPool {
637639
) -> [(Connection.ID, EventLoop)] {
638640
// create new connections for requests with a required event loop
639641

640-
// we may already start connections for those requests and do not want to start to many
642+
// we may already start connections for those requests and do not want to start too many
641643
let startingRequiredEventLoopConnectionCount = Dictionary(
642644
self.connections[self.overflowIndex..<self.connections.endIndex].lazy.map {
643645
($0.eventLoop.id, 1)

Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+StateMachine.swift

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,20 +105,32 @@ extension HTTPConnectionPool {
105105
idGenerator: Connection.ID.Generator,
106106
maximumConcurrentHTTP1Connections: Int,
107107
retryConnectionEstablishment: Bool,
108+
preferHTTP1: Bool,
108109
maximumConnectionUses: Int?
109110
) {
110111
self.maximumConcurrentHTTP1Connections = maximumConcurrentHTTP1Connections
111112
self.retryConnectionEstablishment = retryConnectionEstablishment
112113
self.idGenerator = idGenerator
113114
self.maximumConnectionUses = maximumConnectionUses
114-
let http1State = HTTP1StateMachine(
115-
idGenerator: idGenerator,
116-
maximumConcurrentConnections: maximumConcurrentHTTP1Connections,
117-
retryConnectionEstablishment: retryConnectionEstablishment,
118-
maximumConnectionUses: maximumConnectionUses,
119-
lifecycleState: .running
120-
)
121-
self.state = .http1(http1State)
115+
116+
if preferHTTP1 {
117+
let http1State = HTTP1StateMachine(
118+
idGenerator: idGenerator,
119+
maximumConcurrentConnections: maximumConcurrentHTTP1Connections,
120+
retryConnectionEstablishment: retryConnectionEstablishment,
121+
maximumConnectionUses: maximumConnectionUses,
122+
lifecycleState: .running
123+
)
124+
self.state = .http1(http1State)
125+
} else {
126+
let http2State = HTTP2StateMachine(
127+
idGenerator: idGenerator,
128+
retryConnectionEstablishment: retryConnectionEstablishment,
129+
lifecycleState: .running,
130+
maximumConnectionUses: maximumConnectionUses
131+
)
132+
self.state = .http2(http2State)
133+
}
122134
}
123135

124136
mutating func executeRequest(_ request: Request) -> Action {

Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP1ConnectionsTest.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,34 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase {
408408
XCTAssertTrue(context.eventLoop === el3)
409409
}
410410

411+
func testMigrationFromHTTP2WithPendingRequestsWithRequiredEventLoopSameAsStartingConnections() {
412+
let elg = EmbeddedEventLoopGroup(loops: 4)
413+
let generator = HTTPConnectionPool.Connection.ID.Generator()
414+
var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil)
415+
416+
let el1 = elg.next()
417+
let el2 = elg.next()
418+
419+
let conn1ID = generator.next()
420+
let conn2ID = generator.next()
421+
422+
connections.migrateFromHTTP2(
423+
starting: [(conn1ID, el1)],
424+
backingOff: [(conn2ID, el2)]
425+
)
426+
427+
let stats = connections.stats
428+
XCTAssertEqual(stats.idle, 0)
429+
XCTAssertEqual(stats.leased, 0)
430+
XCTAssertEqual(stats.connecting, 1)
431+
XCTAssertEqual(stats.backingOff, 1)
432+
433+
let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1)
434+
let (_, context) = connections.newHTTP1ConnectionEstablished(conn1)
435+
XCTAssertEqual(context.use, .generalPurpose)
436+
XCTAssertTrue(context.eventLoop === el1)
437+
}
438+
411439
func testMigrationFromHTTP2WithPendingRequestsWithPreferredEventLoop() {
412440
let elg = EmbeddedEventLoopGroup(loops: 4)
413441
let generator = HTTPConnectionPool.Connection.ID.Generator()

Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP1StateTests.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase {
2929
idGenerator: .init(),
3030
maximumConcurrentHTTP1Connections: 8,
3131
retryConnectionEstablishment: true,
32+
preferHTTP1: true,
3233
maximumConnectionUses: nil
3334
)
3435

@@ -113,6 +114,7 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase {
113114
idGenerator: .init(),
114115
maximumConcurrentHTTP1Connections: 8,
115116
retryConnectionEstablishment: false,
117+
preferHTTP1: true,
116118
maximumConnectionUses: nil
117119
)
118120

@@ -181,6 +183,7 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase {
181183
idGenerator: .init(),
182184
maximumConcurrentHTTP1Connections: 2,
183185
retryConnectionEstablishment: true,
186+
preferHTTP1: true,
184187
maximumConnectionUses: nil
185188
)
186189

@@ -240,6 +243,7 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase {
240243
idGenerator: .init(),
241244
maximumConcurrentHTTP1Connections: 2,
242245
retryConnectionEstablishment: true,
246+
preferHTTP1: true,
243247
maximumConnectionUses: nil
244248
)
245249

@@ -278,6 +282,7 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase {
278282
idGenerator: .init(),
279283
maximumConcurrentHTTP1Connections: 2,
280284
retryConnectionEstablishment: true,
285+
preferHTTP1: true,
281286
maximumConnectionUses: nil
282287
)
283288

@@ -670,6 +675,7 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase {
670675
idGenerator: .init(),
671676
maximumConcurrentHTTP1Connections: 6,
672677
retryConnectionEstablishment: true,
678+
preferHTTP1: true,
673679
maximumConnectionUses: nil
674680
)
675681

@@ -710,6 +716,7 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase {
710716
idGenerator: .init(),
711717
maximumConcurrentHTTP1Connections: 6,
712718
retryConnectionEstablishment: true,
719+
preferHTTP1: true,
713720
maximumConnectionUses: nil
714721
)
715722

@@ -743,6 +750,7 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase {
743750
idGenerator: .init(),
744751
maximumConcurrentHTTP1Connections: 6,
745752
retryConnectionEstablishment: true,
753+
preferHTTP1: true,
746754
maximumConnectionUses: nil
747755
)
748756

@@ -768,6 +776,7 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase {
768776
idGenerator: .init(),
769777
maximumConcurrentHTTP1Connections: 6,
770778
retryConnectionEstablishment: true,
779+
preferHTTP1: true,
771780
maximumConnectionUses: nil
772781
)
773782

Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2StateMachineTests.swift

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
720720
idGenerator: .init(),
721721
maximumConcurrentHTTP1Connections: 8,
722722
retryConnectionEstablishment: true,
723+
preferHTTP1: true,
723724
maximumConnectionUses: nil
724725
)
725726

@@ -811,6 +812,7 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
811812
idGenerator: .init(),
812813
maximumConcurrentHTTP1Connections: 8,
813814
retryConnectionEstablishment: true,
815+
preferHTTP1: false,
814816
maximumConnectionUses: nil
815817
)
816818

@@ -858,6 +860,7 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
858860
idGenerator: .init(),
859861
maximumConcurrentHTTP1Connections: 8,
860862
retryConnectionEstablishment: true,
863+
preferHTTP1: true,
861864
maximumConnectionUses: nil
862865
)
863866

@@ -998,6 +1001,7 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
9981001
idGenerator: .init(),
9991002
maximumConcurrentHTTP1Connections: 8,
10001003
retryConnectionEstablishment: true,
1004+
preferHTTP1: false,
10011005
maximumConnectionUses: nil
10021006
)
10031007

@@ -1014,11 +1018,11 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
10141018
XCTAssertNoThrow(try queuer.queue(mockRequest, id: request1.id))
10151019
let http2Conn: HTTPConnectionPool.Connection = .__testOnly_connection(id: http2ConnID, eventLoop: el1)
10161020
XCTAssertNoThrow(try connections.succeedConnectionCreationHTTP2(http2ConnID, maxConcurrentStreams: 10))
1017-
let migrationAction1 = state.newHTTP2ConnectionCreated(http2Conn, maxConcurrentStreams: 10)
1018-
guard case .executeRequestsAndCancelTimeouts(let requests, http2Conn) = migrationAction1.request else {
1019-
return XCTFail("unexpected request action \(migrationAction1.request)")
1021+
let executeAction1 = state.newHTTP2ConnectionCreated(http2Conn, maxConcurrentStreams: 10)
1022+
guard case .executeRequestsAndCancelTimeouts(let requests, http2Conn) = executeAction1.request else {
1023+
return XCTFail("unexpected request action \(executeAction1.request)")
10201024
}
1021-
XCTAssertEqual(migrationAction1.connection, .migration(createConnections: [], closeConnections: [], scheduleTimeout: nil))
1025+
10221026
XCTAssertEqual(requests.count, 1)
10231027
for request in requests {
10241028
XCTAssertNoThrow(try queuer.get(request.id, request: request.__testOnly_wrapped_request()))
@@ -1069,6 +1073,7 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
10691073
idGenerator: .init(),
10701074
maximumConcurrentHTTP1Connections: 8,
10711075
retryConnectionEstablishment: true,
1076+
preferHTTP1: false,
10721077
maximumConnectionUses: nil
10731078
)
10741079

@@ -1085,11 +1090,11 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
10851090
XCTAssertNoThrow(try queuer.queue(mockRequest, id: request1.id))
10861091
let http2Conn: HTTPConnectionPool.Connection = .__testOnly_connection(id: http2ConnID, eventLoop: el1)
10871092
XCTAssertNoThrow(try connections.succeedConnectionCreationHTTP2(http2ConnID, maxConcurrentStreams: 10))
1088-
let migrationAction1 = state.newHTTP2ConnectionCreated(http2Conn, maxConcurrentStreams: 10)
1089-
guard case .executeRequestsAndCancelTimeouts(let requests, http2Conn) = migrationAction1.request else {
1090-
return XCTFail("unexpected request action \(migrationAction1.request)")
1093+
let executeAction1 = state.newHTTP2ConnectionCreated(http2Conn, maxConcurrentStreams: 10)
1094+
guard case .executeRequestsAndCancelTimeouts(let requests, http2Conn) = executeAction1.request else {
1095+
return XCTFail("unexpected request action \(executeAction1.request)")
10911096
}
1092-
XCTAssertEqual(migrationAction1.connection, .migration(createConnections: [], closeConnections: [], scheduleTimeout: nil))
1097+
10931098
XCTAssertEqual(requests.count, 1)
10941099
for request in requests {
10951100
XCTAssertNoThrow(try queuer.get(request.id, request: request.__testOnly_wrapped_request()))
@@ -1120,7 +1125,7 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
11201125
}
11211126
XCTAssertTrue(queuer.isEmpty)
11221127

1123-
// if we established a new http/1 connection we should migrate back to http/1,
1128+
// if we established a new http/1 connection we should migrate to http/1,
11241129
// close the connection and shutdown the pool
11251130
let http1Conn: HTTPConnectionPool.Connection = .__testOnly_connection(id: http1ConnId, eventLoop: el2)
11261131
XCTAssertNoThrow(try connections.succeedConnectionCreationHTTP1(http1ConnId))
@@ -1146,11 +1151,12 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
11461151
idGenerator: .init(),
11471152
maximumConcurrentHTTP1Connections: 8,
11481153
retryConnectionEstablishment: true,
1154+
preferHTTP1: false,
11491155
maximumConnectionUses: nil
11501156
)
11511157

11521158
var connectionIDs: [HTTPConnectionPool.Connection.ID] = []
1153-
for el in [el1, el2, el2] {
1159+
for el in [el1, el2] {
11541160
let mockRequest = MockHTTPScheduableRequest(eventLoop: el, requiresEventLoopForChannel: true)
11551161
let request = HTTPConnectionPool.Request(mockRequest)
11561162
let action = state.executeRequest(request)
@@ -1164,7 +1170,7 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
11641170
XCTAssertNoThrow(try queuer.queue(mockRequest, id: request.id))
11651171
}
11661172

1167-
// fail the two connections for el2
1173+
// fail the connection for el2
11681174
for connectionID in connectionIDs.dropFirst() {
11691175
struct SomeError: Error {}
11701176
XCTAssertNoThrow(try connections.failConnectionCreation(connectionID))
@@ -1177,16 +1183,14 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
11771183
}
11781184
let http2ConnID1 = connectionIDs[0]
11791185
let http2ConnID2 = connectionIDs[1]
1180-
let http2ConnID3 = connectionIDs[2]
11811186

11821187
// let the first connection on el1 succeed as a http2 connection
11831188
let http2Conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: http2ConnID1, eventLoop: el1)
11841189
XCTAssertNoThrow(try connections.succeedConnectionCreationHTTP2(http2ConnID1, maxConcurrentStreams: 10))
1185-
let migrationAction1 = state.newHTTP2ConnectionCreated(http2Conn1, maxConcurrentStreams: 10)
1186-
guard case .executeRequestsAndCancelTimeouts(let requests, http2Conn1) = migrationAction1.request else {
1187-
return XCTFail("unexpected request action \(migrationAction1.request)")
1190+
let connectionAction = state.newHTTP2ConnectionCreated(http2Conn1, maxConcurrentStreams: 10)
1191+
guard case .executeRequestsAndCancelTimeouts(let requests, http2Conn1) = connectionAction.request else {
1192+
return XCTFail("unexpected request action \(connectionAction.request)")
11881193
}
1189-
XCTAssertEqual(migrationAction1.connection, .migration(createConnections: [], closeConnections: [], scheduleTimeout: nil))
11901194
XCTAssertEqual(requests.count, 1)
11911195
for request in requests {
11921196
XCTAssertNoThrow(try queuer.get(request.id, request: request.__testOnly_wrapped_request()))
@@ -1205,14 +1209,6 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
12051209
}
12061210
XCTAssertTrue(eventLoop2 === el2)
12071211
XCTAssertNoThrow(try connections.createConnection(newHttp2ConnID2, on: el2))
1208-
1209-
// we now have a starting connection for el2 and another one backing off
1210-
1211-
// if the backoff timer fires now for a connection on el2, we should *not* start a new connection
1212-
XCTAssertNoThrow(try connections.connectionBackoffTimerDone(http2ConnID3))
1213-
let action3 = state.connectionCreationBackoffDone(http2ConnID3)
1214-
XCTAssertEqual(action3.request, .none)
1215-
XCTAssertEqual(action3.connection, .none)
12161212
}
12171213

12181214
func testMaxConcurrentStreamsIsRespected() {

0 commit comments

Comments
 (0)