Skip to content

Commit dfd000d

Browse files
committed
fix review comments from @fabianfett
1 parent 6eda17f commit dfd000d

File tree

2 files changed

+68
-30
lines changed

2 files changed

+68
-30
lines changed

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

+58-20
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ extension HTTPConnectionPool {
4949
}
5050
}
5151

52+
var canOrWillBeAbleToExecuteRequests: Bool {
53+
switch self.state {
54+
case .starting, .backingOff, .active:
55+
return true
56+
case .draining, .closed:
57+
return false
58+
}
59+
}
60+
5261
/// A request can be scheduled on the connection
5362
var isAvailable: Bool {
5463
switch self.state {
@@ -92,6 +101,7 @@ extension HTTPConnectionPool {
92101
switch self.state {
93102
case .active, .draining, .backingOff, .closed:
94103
preconditionFailure("Invalid state: \(self.state)")
104+
95105
case .starting:
96106
self.state = .active(conn, maxStreams: maxStreams, usedStreams: 0, lastIdle: .now())
97107
return maxStreams
@@ -106,25 +116,29 @@ extension HTTPConnectionPool {
106116
switch self.state {
107117
case .starting, .backingOff, .closed:
108118
preconditionFailure("Invalid state for updating max concurrent streams: \(self.state)")
109-
case .draining(let conn, _, let usedStreams):
110-
self.state = .draining(conn, maxStreams: maxStreams, usedStreams: usedStreams)
111-
return 0
119+
112120
case .active(let conn, _, let usedStreams, let lastIdle):
113121
self.state = .active(conn, maxStreams: maxStreams, usedStreams: usedStreams, lastIdle: lastIdle)
114122
return max(maxStreams - usedStreams, 0)
123+
124+
case .draining(let conn, _, let usedStreams):
125+
self.state = .draining(conn, maxStreams: maxStreams, usedStreams: usedStreams)
126+
return 0
115127
}
116128
}
117129

118130
mutating func goAwayReceived() -> EventLoop {
119131
switch self.state {
120132
case .starting, .backingOff, .closed:
121133
preconditionFailure("Invalid state for draining a connection: \(self.state)")
122-
case .draining(let conn, _, _):
123-
// we could potentially receive another go away while we drain all active streams and we just ignore it
124-
return conn.eventLoop
134+
125135
case .active(let conn, let maxStreams, let usedStreams, _):
126136
self.state = .draining(conn, maxStreams: maxStreams, usedStreams: usedStreams)
127137
return conn.eventLoop
138+
139+
case .draining(let conn, _, _):
140+
// we could potentially receive another go away while we drain all active streams and we just ignore it
141+
return conn.eventLoop
128142
}
129143
}
130144

@@ -151,6 +165,7 @@ extension HTTPConnectionPool {
151165
switch self.state {
152166
case .starting, .backingOff, .draining, .closed:
153167
preconditionFailure("Invalid state for leasing a stream: \(self.state)")
168+
154169
case .active(let conn, let maxStreams, var usedStreams, let lastIdle):
155170
usedStreams += count
156171
precondition(usedStreams <= maxStreams, "tried to lease a connection which is not available")
@@ -165,11 +180,7 @@ extension HTTPConnectionPool {
165180
switch self.state {
166181
case .starting, .backingOff, .closed:
167182
preconditionFailure("Invalid state: \(self.state)")
168-
case .draining(let conn, let maxStreams, var usedStreams):
169-
usedStreams -= 1
170-
assert(usedStreams >= 0)
171-
self.state = .draining(conn, maxStreams: maxStreams, usedStreams: usedStreams)
172-
return 0
183+
173184
case .active(let conn, let maxStreams, var usedStreams, var lastIdle):
174185
usedStreams -= 1
175186
assert(usedStreams >= 0)
@@ -178,6 +189,12 @@ extension HTTPConnectionPool {
178189
}
179190
self.state = .active(conn, maxStreams: maxStreams, usedStreams: usedStreams, lastIdle: lastIdle)
180191
return max(maxStreams - usedStreams, 0)
192+
193+
case .draining(let conn, let maxStreams, var usedStreams):
194+
usedStreams -= 1
195+
assert(usedStreams >= 0)
196+
self.state = .draining(conn, maxStreams: maxStreams, usedStreams: usedStreams)
197+
return 0
181198
}
182199
}
183200

@@ -186,6 +203,7 @@ extension HTTPConnectionPool {
186203
case .active(let conn, _, 0, _):
187204
self.state = .closed
188205
return conn
206+
189207
case .starting, .backingOff, .draining, .closed, .active:
190208
preconditionFailure("Invalid state for closing a connection: \(self.state)")
191209
}
@@ -214,9 +232,11 @@ extension HTTPConnectionPool {
214232
switch self.state {
215233
case .starting:
216234
return .keepConnection
235+
217236
case .backingOff:
218237
context.connectBackoff.append(self.connectionID)
219238
return .removeConnection
239+
220240
case .active(let connection, _, let usedStreams, _):
221241
if usedStreams <= 0 {
222242
context.close.append(connection)
@@ -225,9 +245,11 @@ extension HTTPConnectionPool {
225245
context.cancel.append(connection)
226246
return .keepConnection
227247
}
248+
228249
case .draining(let connection, _, _):
229250
context.cancel.append(connection)
230251
return .keepConnection
252+
231253
case .closed:
232254
preconditionFailure("Unexpected state for cleanup: Did not expect to have closed connections in the state machine.")
233255
}
@@ -240,15 +262,19 @@ extension HTTPConnectionPool {
240262
switch self.state {
241263
case .starting:
242264
stats.startingConnections &+= 1
265+
243266
case .backingOff:
244267
stats.backingOffConnections &+= 1
268+
245269
case .active(_, let maxStreams, let usedStreams, _):
246270
stats.availableStreams += max(maxStreams - usedStreams, 0)
247271
stats.leasedStreams += usedStreams
248272
stats.availableConnections &+= 1
273+
249274
case .draining(_, _, let usedStreams):
250275
stats.drainingConnections &+= 1
251276
stats.leasedStreams += usedStreams
277+
252278
case .closed:
253279
break
254280
}
@@ -310,21 +336,23 @@ extension HTTPConnectionPool {
310336

311337
/// used in general purpose connection scenarios to check if at least one connection exist, or if should we create a new one
312338
var hasConnectionThatCanOrWillBeAbleToExecuteRequests: Bool {
313-
self.connections.contains { $0.isStartingOrBackingOff || $0.isActive }
339+
self.connections.contains { $0.canOrWillBeAbleToExecuteRequests }
314340
}
315341

316342
/// used in eventLoop scenarios. does at least one connection exist for this eventLoop, or should we create a new one?
317343
/// - Parameter eventLoop: connection `EventLoop` to search for
318344
/// - Returns: true if at least one connection is starting or active for the given `eventLoop`
319345
func hasConnectionThatCanOrWillBeAbleToExecuteRequests(for eventLoop: EventLoop) -> Bool {
320346
self.connections.contains {
321-
$0.eventLoop === eventLoop && ($0.isStartingOrBackingOff || $0.isActive)
347+
$0.eventLoop === eventLoop && $0.canOrWillBeAbleToExecuteRequests
322348
}
323349
}
324350

325351
mutating func createNewConnection(on eventLoop: EventLoop) -> Connection.ID {
326-
// assert no active connection exists on the requested eventLoop
327-
assert(self.connections.allSatisfy { $0.eventLoop !== eventLoop || !$0.isActive })
352+
assert(
353+
!self.hasConnectionThatCanOrWillBeAbleToExecuteRequests(for: eventLoop),
354+
"we should not create more than one connection per event loop"
355+
)
328356

329357
let connection = HTTP2ConnectionState(connectionID: self.generator.next(), eventLoop: eventLoop)
330358
self.connections.append(connection)
@@ -353,10 +381,11 @@ extension HTTPConnectionPool {
353381
return (index, context)
354382
}
355383

356-
/// Move the HTTP1ConnectionState to backingOff.
384+
/// Move the connection state to backingOff.
357385
///
358386
/// - Parameter connectionID: The connectionID of the failed connection attempt
359387
/// - Returns: The eventLoop on which to schedule the backoff timer
388+
/// - Precondition: connection needs to be in the `.starting` state
360389
mutating func backoffNextConnectionAttempt(_ connectionID: Connection.ID) -> EventLoop {
361390
guard let index = self.connections.firstIndex(where: { $0.connectionID == connectionID }) else {
362391
preconditionFailure("We tried to create a new connection that we know nothing about?")
@@ -368,6 +397,9 @@ extension HTTPConnectionPool {
368397

369398
// MARK: Connection lifecycle events
370399

400+
/// Sets the connection with the given `connectionId` to the draining state.
401+
/// - Returns: the `EventLoop` to create a new connection on if applicable
402+
/// - Precondition: connection with given `connectionId` must be either `.active` or already in the `.draining` state
371403
mutating func goAwayReceived(_ connectionID: Connection.ID) -> GoAwayContext {
372404
guard let index = self.connections.firstIndex(where: { $0.connectionID == connectionID }) else {
373405
preconditionFailure("go away recieved for a connection that does not exists")
@@ -376,12 +408,18 @@ extension HTTPConnectionPool {
376408
return GoAwayContext(eventLoop: eventLoop)
377409
}
378410

411+
/// Update the maximum number of concurrent streams for the given connection.
412+
/// - Parameters:
413+
/// - connectionID: The connectionID for which we received new settings
414+
/// - newMaxStreams: new maximum concurrent streams
415+
/// - Returns: index of the connection and new number of available streams in the `AvailableConnectionContext`
416+
/// - Precondition: Connections must be in the `.active` or `.draining` state.
379417
mutating func newHTTP2MaxConcurrentStreamsReceived(
380418
_ connectionID: Connection.ID,
381419
newMaxStreams: Int
382420
) -> (Int, AvailableConnectionContext) {
383421
guard let index = self.connections.firstIndex(where: { $0.connectionID == connectionID }) else {
384-
preconditionFailure("We tried to update the maximum concurren streams number for a connection that does not exists")
422+
preconditionFailure("We tried to update the maximum number of concurrent streams for a connection that does not exists")
385423
}
386424
let availableStreams = self.connections[index].newMaxConcurrentStreams(newMaxStreams)
387425
let context = AvailableConnectionContext(
@@ -413,7 +451,7 @@ extension HTTPConnectionPool {
413451
return availableConnection
414452
}
415453

416-
mutating func leaseStreams(onRequired eventLoop: EventLoop) -> Connection? {
454+
mutating func leaseStream(onRequired eventLoop: EventLoop) -> Connection? {
417455
guard let index = self.findAvailableConnection(onRequired: eventLoop) else { return nil }
418456
return self.leaseStreams(at: index, count: 1)
419457
}
@@ -453,7 +491,7 @@ extension HTTPConnectionPool {
453491
/// - Returns: closed and removed connection
454492
mutating func closeConnection(at index: Int) -> Connection {
455493
let connection = self.connections[index].close()
456-
self.connections.remove(at: index)
494+
self.removeConnection(at: index)
457495
return connection
458496
}
459497

@@ -479,7 +517,7 @@ extension HTTPConnectionPool {
479517
}
480518

481519
/// replaces a closed connection by creating a new starting connection.
482-
/// - Parameter index: index of the connection which we get from `failConnection(_:)`
520+
/// - Parameter index: index of the connection which we got from `failConnection(_:)`
483521
/// - Precondition: connection must be closed
484522
mutating func createNewConnectionByReplacingClosedConnection(at index: Int) -> (Connection.ID, EventLoop) {
485523
precondition(self.connections[index].isClosed)

Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2ConnectionsTest.swift

+10-10
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase {
164164
XCTAssert(conn1CreatedContext.eventLoop === el)
165165
}
166166

167-
XCTAssertNil(connections.leaseStreams(onRequired: el5))
167+
XCTAssertNil(connections.leaseStream(onRequired: el5))
168168
}
169169

170170
func testCloseConnectionIfIdle() {
@@ -238,7 +238,7 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase {
238238
_ = connections.newHTTP2ConnectionEstablished(conn1, maxConcurrentStreams: 100)
239239

240240
// we lease it just before timeout
241-
XCTAssertEqual(connections.leaseStreams(onRequired: el1), conn1)
241+
XCTAssertEqual(connections.leaseStream(onRequired: el1), conn1)
242242

243243
// timeout arrives minimal to late
244244
XCTAssertEqual(connections.closeConnectionIfIdle(conn1ID), nil)
@@ -324,14 +324,14 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase {
324324
XCTAssertEqual(conn1CreatedContext.availableStreams, 100)
325325
XCTAssertEqual(connections.leaseStreams(at: conn1Index, count: 100), conn1)
326326

327-
XCTAssertNil(connections.leaseStreams(onRequired: el1), "should not be able to lease stream because they are all already leased")
327+
XCTAssertNil(connections.leaseStream(onRequired: el1), "should not be able to lease stream because they are all already leased")
328328

329329
let (_, releaseContext) = connections.releaseStream(conn1ID)
330330
XCTAssertFalse(releaseContext.isIdle)
331331
XCTAssertEqual(releaseContext.availableStreams, 1)
332332

333-
XCTAssertEqual(connections.leaseStreams(onRequired: el1), conn1)
334-
XCTAssertNil(connections.leaseStreams(onRequired: el1), "should not be able to lease stream because they are all already leased")
333+
XCTAssertEqual(connections.leaseStream(onRequired: el1), conn1)
334+
XCTAssertNil(connections.leaseStream(onRequired: el1), "should not be able to lease stream because they are all already leased")
335335
}
336336

337337
func testGoAway() {
@@ -360,7 +360,7 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase {
360360
)
361361
)
362362

363-
XCTAssertNil(connections.leaseStreams(onRequired: el1), "we should not be able to lease a stream because the connection is draining")
363+
XCTAssertNil(connections.leaseStream(onRequired: el1), "we should not be able to lease a stream because the connection is draining")
364364

365365
// a server can potentially send more than one connection go away and we should not crash
366366
XCTAssertTrue(connections.goAwayReceived(conn1ID).eventLoop === el1)
@@ -423,14 +423,14 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase {
423423
XCTAssertEqual(conn1CreatedContext.availableStreams, 1)
424424
XCTAssertEqual(connections.leaseStreams(at: conn1Index, count: 1), conn1)
425425

426-
XCTAssertNil(connections.leaseStreams(onRequired: el1), "all streams are in use")
426+
XCTAssertNil(connections.leaseStream(onRequired: el1), "all streams are in use")
427427

428428
let (_, newSettingsContext1) = connections.newHTTP2MaxConcurrentStreamsReceived(conn1ID, newMaxStreams: 2)
429429
XCTAssertEqual(newSettingsContext1.availableStreams, 1)
430430
XCTAssertTrue(newSettingsContext1.eventLoop === el1)
431431
XCTAssertFalse(newSettingsContext1.isIdle)
432432

433-
XCTAssertEqual(connections.leaseStreams(onRequired: el1), conn1)
433+
XCTAssertEqual(connections.leaseStream(onRequired: el1), conn1)
434434

435435
let (_, newSettingsContext2) = connections.newHTTP2MaxConcurrentStreamsReceived(conn1ID, newMaxStreams: 1)
436436
XCTAssertEqual(newSettingsContext2.availableStreams, 0)
@@ -442,14 +442,14 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase {
442442
XCTAssertFalse(release1Context.isIdle)
443443
XCTAssertEqual(release1Context.availableStreams, 0)
444444

445-
XCTAssertNil(connections.leaseStreams(onRequired: el1), "all streams are in use")
445+
XCTAssertNil(connections.leaseStream(onRequired: el1), "all streams are in use")
446446

447447
// release a connection
448448
let (_, release2Context) = connections.releaseStream(conn1ID)
449449
XCTAssertTrue(release2Context.isIdle)
450450
XCTAssertEqual(release2Context.availableStreams, 1)
451451

452-
XCTAssertEqual(connections.leaseStreams(onRequired: el1), conn1)
452+
XCTAssertEqual(connections.leaseStream(onRequired: el1), conn1)
453453
}
454454

455455
func testLeaseOnPreferredEventLoopWithoutAnyAvailable() {

0 commit comments

Comments
 (0)