Skip to content

Commit 054fca8

Browse files
fabianfettLukasa
andcommitted
Update Tests/AsyncHTTPClientTests/Mocks/MockConnectionPool.swift
Co-authored-by: Cory Benfield <[email protected]>
1 parent a75a7cd commit 054fca8

File tree

2 files changed

+136
-87
lines changed

2 files changed

+136
-87
lines changed

Diff for: Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1Connections.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ extension HTTPConnectionPool {
8080

8181
var idleSince: NIODeadline? {
8282
switch self.state {
83-
case .idle(_, since: let lastReturn):
84-
return lastReturn
83+
case .idle(_, since: let idleSince):
84+
return idleSince
8585
case .backingOff, .starting, .leased, .closed:
8686
return nil
8787
}

Diff for: Tests/AsyncHTTPClientTests/Mocks/MockConnectionPool.swift

+134-85
Original file line numberDiff line numberDiff line change
@@ -22,37 +22,46 @@ import NIOHTTP1
2222
struct MockConnectionPool {
2323
typealias Connection = HTTPConnectionPool.Connection
2424

25-
enum Errors: Error {
25+
enum Errors: Error, Hashable {
2626
case connectionIDAlreadyUsed
2727
case connectionNotFound
2828
case connectionExists
2929
case connectionNotIdle
30-
case connectionAlreadyParked
3130
case connectionNotParked
3231
case connectionIsParked
3332
case connectionIsClosed
3433
case connectionIsNotStarting
3534
case connectionIsNotExecuting
3635
case connectionDoesNotFulfillEventLoopRequirement
36+
case connectionDoesNotHaveHTTP2StreamAvailable
3737
case connectionBackoffTimerExists
3838
case connectionBackoffTimerNotFound
3939
}
4040

41-
private struct MockConnectionState {
41+
fileprivate struct MockConnectionState {
4242
typealias ID = HTTPConnectionPool.Connection.ID
4343

44-
enum State {
44+
private enum State {
45+
enum HTTP1State {
46+
case inUse
47+
case idle(parked: Bool, idleSince: NIODeadline)
48+
}
49+
50+
enum HTTP2State {
51+
case inUse(maxConcurrentStreams: Int, used: Int)
52+
case idle(maxConcurrentStreams: Int, parked: Bool, lastIdle: NIODeadline)
53+
}
54+
4555
case starting
46-
case http1(leased: Bool, lastIdle: NIODeadline)
47-
case http2(streams: Int, used: Int)
56+
case http1(HTTP1State)
57+
case http2(HTTP2State)
4858
case closed
4959
}
5060

5161
let id: ID
5262
let eventLoop: EventLoop
5363

54-
private(set) var state: State = .starting
55-
private(set) var isParked: Bool = false
64+
private var state: State = .starting
5665

5766
init(id: ID, eventLoop: EventLoop) {
5867
self.id = id
@@ -68,36 +77,60 @@ struct MockConnectionPool {
6877
}
6978
}
7079

80+
/// Is the connection idle (meaning there are no requests executing on it)
7181
var isIdle: Bool {
7282
switch self.state {
73-
case .starting, .closed:
83+
case .starting, .closed, .http1(.inUse), .http2(.inUse):
7484
return false
75-
case .http1(let leased, _):
76-
return !leased
77-
case .http2(_, let used):
78-
return used == 0
85+
86+
case .http1(.idle), .http2(.idle):
87+
return true
7988
}
8089
}
8190

82-
var isLeased: Bool {
91+
/// Is the connection available (can another request be executed on it)
92+
var isAvailable: Bool {
8393
switch self.state {
84-
case .starting, .closed:
94+
case .starting, .closed, .http1(.inUse):
8595
return false
86-
case .http1(let leased, _):
87-
return leased
88-
case .http2(_, let used):
89-
return used > 0
96+
97+
case .http2(.inUse(let maxStreams, let used)):
98+
return used < maxStreams
99+
100+
case .http1(.idle), .http2(.idle):
101+
return true
90102
}
91103
}
92104

93-
var lastIdle: NIODeadline? {
105+
/// Is the connection idle and did we create a timeout timer for it?
106+
var isParked: Bool {
94107
switch self.state {
95-
case .starting, .closed:
96-
return nil
97-
case .http1(_, let lastReturn):
98-
return lastReturn
99-
case .http2:
108+
case .starting, .closed, .http1(.inUse), .http2(.inUse):
109+
return false
110+
111+
case .http1(.idle(let parked, _)), .http2(.idle(_, let parked, _)):
112+
return parked
113+
}
114+
}
115+
116+
/// Is the connection in use (are there requests executing on it)
117+
var isUsed: Bool {
118+
switch self.state {
119+
case .starting, .closed, .http1(.idle), .http2(.idle):
120+
return false
121+
122+
case .http1(.inUse), .http2(.inUse):
123+
return true
124+
}
125+
}
126+
127+
var idleSince: NIODeadline? {
128+
switch self.state {
129+
case .starting, .closed, .http1(.inUse), .http2(.inUse):
100130
return nil
131+
132+
case .http1(.idle(_, let lastIdle)), .http2(.idle(_, _, let lastIdle)):
133+
return lastIdle
101134
}
102135
}
103136

@@ -106,76 +139,93 @@ struct MockConnectionPool {
106139
throw Errors.connectionIsNotStarting
107140
}
108141

109-
self.state = .http1(leased: false, lastIdle: .now())
142+
self.state = .http1(.idle(parked: false, idleSince: .now()))
110143
}
111144

112145
mutating func park() throws {
113-
guard self.isIdle else {
146+
switch self.state {
147+
case .starting, .closed, .http1(.inUse), .http2(.inUse):
114148
throw Errors.connectionNotIdle
115-
}
116149

117-
guard !self.isParked else {
118-
throw Errors.connectionAlreadyParked
119-
}
150+
case .http1(.idle(true, _)), .http2(.idle(_, true, _)):
151+
throw Errors.connectionIsParked
120152

121-
self.isParked = true
153+
case .http1(.idle(false, let lastIdle)):
154+
self.state = .http1(.idle(parked: true, idleSince: lastIdle))
155+
156+
case .http2(.idle(let maxStreams, false, let lastIdle)):
157+
self.state = .http2(.idle(maxConcurrentStreams: maxStreams, parked: true, lastIdle: lastIdle))
158+
}
122159
}
123160

124161
mutating func activate() throws {
125-
guard self.isIdle else {
162+
switch self.state {
163+
case .starting, .closed, .http1(.inUse), .http2(.inUse):
126164
throw Errors.connectionNotIdle
127-
}
128165

129-
guard self.isParked else {
166+
case .http1(.idle(false, _)), .http2(.idle(_, false, _)):
130167
throw Errors.connectionNotParked
131-
}
132168

133-
self.isParked = false
169+
case .http1(.idle(true, let lastIdle)):
170+
self.state = .http1(.idle(parked: false, idleSince: lastIdle))
171+
172+
case .http2(.idle(let maxStreams, true, let lastIdle)):
173+
self.state = .http2(.idle(maxConcurrentStreams: maxStreams, parked: false, lastIdle: lastIdle))
174+
}
134175
}
135176

136177
mutating func execute(_ request: HTTPSchedulableRequest) throws {
137-
guard !self.isParked else {
178+
switch self.state {
179+
case .starting, .http1(.inUse):
180+
throw Errors.connectionNotIdle
181+
182+
case .http1(.idle(true, _)), .http2(.idle(_, true, _)):
138183
throw Errors.connectionIsParked
139-
}
140184

141-
if let required = request.requiredEventLoop, required !== self.eventLoop {
142-
throw Errors.connectionDoesNotFulfillEventLoopRequirement
143-
}
185+
case .http1(.idle(false, _)):
186+
if let required = request.requiredEventLoop, required !== self.eventLoop {
187+
throw Errors.connectionDoesNotFulfillEventLoopRequirement
188+
}
189+
self.state = .http1(.inUse)
190+
191+
case .http2(.idle(let maxStreams, false, _)):
192+
if let required = request.requiredEventLoop, required !== self.eventLoop {
193+
throw Errors.connectionDoesNotFulfillEventLoopRequirement
194+
}
195+
if maxStreams < 1 {
196+
throw Errors.connectionDoesNotHaveHTTP2StreamAvailable
197+
}
198+
self.state = .http2(.inUse(maxConcurrentStreams: maxStreams, used: 1))
199+
200+
case .http2(.inUse(let maxStreams, let used)):
201+
if let required = request.requiredEventLoop, required !== self.eventLoop {
202+
throw Errors.connectionDoesNotFulfillEventLoopRequirement
203+
}
204+
if used + 1 > maxStreams {
205+
throw Errors.connectionDoesNotHaveHTTP2StreamAvailable
206+
}
207+
self.state = .http2(.inUse(maxConcurrentStreams: maxStreams, used: used + 1))
144208

145-
switch self.state {
146-
case .starting:
147-
preconditionFailure("Should be unreachable")
148-
case .http1(leased: true, _):
149-
throw Errors.connectionNotIdle
150-
case .http1(leased: false, let lastIdle):
151-
self.state = .http1(leased: true, lastIdle: lastIdle)
152-
case .http2(let streams, let used) where used >= streams:
153-
throw Errors.connectionNotIdle
154-
case .http2(let streams, var used):
155-
used += 1
156-
self.state = .http2(streams: streams, used: used)
157209
case .closed:
158210
throw Errors.connectionIsClosed
159211
}
160212
}
161213

162214
mutating func finishRequest() throws {
163-
guard !self.isParked else {
164-
throw Errors.connectionIsParked
165-
}
166-
167215
switch self.state {
168-
case .starting:
169-
throw Errors.connectionIsNotExecuting
170-
case .http1(leased: true, _):
171-
self.state = .http1(leased: false, lastIdle: .now())
172-
case .http1(leased: false, _):
216+
case .starting, .http1(.idle), .http2(.idle):
173217
throw Errors.connectionIsNotExecuting
174-
case .http2(_, let used) where used <= 0:
175-
throw Errors.connectionIsNotExecuting
176-
case .http2(let streams, var used):
177-
used -= 1
178-
self.state = .http2(streams: streams, used: used)
218+
219+
case .http1(.inUse):
220+
self.state = .http1(.idle(parked: false, idleSince: .now()))
221+
222+
case .http2(.inUse(let maxStreams, let used)):
223+
if used == 1 {
224+
self.state = .http2(.idle(maxConcurrentStreams: maxStreams, parked: false, lastIdle: .now()))
225+
} else {
226+
self.state = .http2(.inUse(maxConcurrentStreams: maxStreams, used: used))
227+
}
228+
179229
case .closed:
180230
throw Errors.connectionIsClosed
181231
}
@@ -185,14 +235,12 @@ struct MockConnectionPool {
185235
switch self.state {
186236
case .starting:
187237
throw Errors.connectionNotIdle
188-
case .http1(let leased, _):
189-
if leased {
190-
throw Errors.connectionNotIdle
191-
}
192-
case .http2(_, let used):
193-
if used > 0 {
194-
throw Errors.connectionNotIdle
195-
}
238+
case .http1(.idle), .http2(.idle):
239+
self.state = .closed
240+
241+
case .http1(.inUse), .http2(.inUse):
242+
throw Errors.connectionNotIdle
243+
196244
case .closed:
197245
throw Errors.connectionIsClosed
198246
}
@@ -209,7 +257,7 @@ struct MockConnectionPool {
209257
}
210258

211259
var leased: Int {
212-
self.connections.values.filter { $0.isLeased }.count
260+
self.connections.values.filter { $0.isUsed }.count
213261
}
214262

215263
var starting: Int {
@@ -227,22 +275,21 @@ struct MockConnectionPool {
227275
var newestParkedConnection: Connection? {
228276
self.connections.values
229277
.filter { $0.isParked }
230-
.max(by: { $0.lastIdle! < $1.lastIdle! })
278+
.max(by: { $0.idleSince! < $1.idleSince! })
231279
.flatMap { .__testOnly_connection(id: $0.id, eventLoop: $0.eventLoop) }
232280
}
233281

234282
var oldestParkedConnection: Connection? {
235283
self.connections.values
236284
.filter { $0.isParked }
237-
.min(by: { $0.lastIdle! < $1.lastIdle! })
285+
.min(by: { $0.idleSince! < $1.idleSince! })
238286
.flatMap { .__testOnly_connection(id: $0.id, eventLoop: $0.eventLoop) }
239287
}
240288

241289
func newestParkedConnection(for eventLoop: EventLoop) -> Connection? {
242290
self.connections.values
243291
.filter { $0.eventLoop === eventLoop && $0.isParked }
244-
.sorted(by: { $0.lastIdle! > $1.lastIdle! })
245-
.max(by: { $0.lastIdle! < $1.lastIdle! })
292+
.max(by: { $0.idleSince! < $1.idleSince! })
246293
.flatMap { .__testOnly_connection(id: $0.id, eventLoop: $0.eventLoop) }
247294
}
248295

@@ -254,7 +301,7 @@ struct MockConnectionPool {
254301

255302
mutating func createConnection(_ connectionID: Connection.ID, on eventLoop: EventLoop) throws {
256303
guard self.connections[connectionID] == nil else {
257-
throw Errors.connectionIDAlreadyUsed
304+
throw Errors.connectionExists
258305
}
259306
self.connections[connectionID] = .init(id: connectionID, eventLoop: eventLoop)
260307
}
@@ -307,7 +354,7 @@ struct MockConnectionPool {
307354

308355
// MARK: Connection destruction
309356

310-
/// Closing a connection signals intend. For this reason, it is verified, that the connection is not running any
357+
/// Closing a connection signals intent. For this reason, it is verified, that the connection is not running any
311358
/// requests when closing.
312359
mutating func closeConnection(_ connection: Connection) throws {
313360
guard var mockConnection = self.connections.removeValue(forKey: connection.id) else {
@@ -361,7 +408,9 @@ struct MockConnectionPool {
361408
try connection.finishRequest()
362409
self.connections[connectionID] = connection
363410
}
411+
}
364412

413+
extension MockConnectionPool {
365414
mutating func randomStartingConnection() -> Connection.ID? {
366415
self.connections.values
367416
.filter { $0.isStarting }
@@ -371,7 +420,7 @@ struct MockConnectionPool {
371420

372421
mutating func randomActiveConnection() -> Connection.ID? {
373422
self.connections.values
374-
.filter { $0.isLeased || $0.isParked }
423+
.filter { $0.isUsed || $0.isParked }
375424
.randomElement()
376425
.map(\.id)
377426
}
@@ -385,7 +434,7 @@ struct MockConnectionPool {
385434

386435
mutating func randomLeasedConnection() -> Connection? {
387436
self.connections.values
388-
.filter { $0.isLeased }
437+
.filter { $0.isUsed }
389438
.randomElement()
390439
.flatMap { .__testOnly_connection(id: $0.id, eventLoop: $0.eventLoop) }
391440
}

0 commit comments

Comments
 (0)