Skip to content

Commit 7bc934d

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

File tree

2 files changed

+122
-87
lines changed

2 files changed

+122
-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

+120-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,46 @@ 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 idle and did we create a timeout timer for it?
92+
var isParked: Bool {
8393
switch self.state {
84-
case .starting, .closed:
94+
case .starting, .closed, .http1(.inUse), .http2(.inUse):
8595
return false
86-
case .http1(let leased, _):
87-
return leased
88-
case .http2(_, let used):
89-
return used > 0
96+
97+
case .http1(.idle(let parked, _)), .http2(.idle(_, let parked, _)):
98+
return parked
9099
}
91100
}
92101

93-
var lastIdle: NIODeadline? {
102+
/// Is the connection in use (are there requests executing on it)
103+
var isUsed: Bool {
94104
switch self.state {
95-
case .starting, .closed:
96-
return nil
97-
case .http1(_, let lastReturn):
98-
return lastReturn
99-
case .http2:
105+
case .starting, .closed, .http1(.idle), .http2(.idle):
106+
return false
107+
108+
case .http1(.inUse), .http2(.inUse):
109+
return true
110+
}
111+
}
112+
113+
var idleSince: NIODeadline? {
114+
switch self.state {
115+
case .starting, .closed, .http1(.inUse), .http2(.inUse):
100116
return nil
117+
118+
case .http1(.idle(_, let lastIdle)), .http2(.idle(_, _, let lastIdle)):
119+
return lastIdle
101120
}
102121
}
103122

@@ -106,76 +125,93 @@ struct MockConnectionPool {
106125
throw Errors.connectionIsNotStarting
107126
}
108127

109-
self.state = .http1(leased: false, lastIdle: .now())
128+
self.state = .http1(.idle(parked: false, idleSince: .now()))
110129
}
111130

112131
mutating func park() throws {
113-
guard self.isIdle else {
132+
switch self.state {
133+
case .starting, .closed, .http1(.inUse), .http2(.inUse):
114134
throw Errors.connectionNotIdle
115-
}
116135

117-
guard !self.isParked else {
118-
throw Errors.connectionAlreadyParked
119-
}
136+
case .http1(.idle(true, _)), .http2(.idle(_, true, _)):
137+
throw Errors.connectionIsParked
138+
139+
case .http1(.idle(false, let lastIdle)):
140+
self.state = .http1(.idle(parked: true, idleSince: lastIdle))
120141

121-
self.isParked = true
142+
case .http2(.idle(let maxStreams, false, let lastIdle)):
143+
self.state = .http2(.idle(maxConcurrentStreams: maxStreams, parked: true, lastIdle: lastIdle))
144+
}
122145
}
123146

124147
mutating func activate() throws {
125-
guard self.isIdle else {
148+
switch self.state {
149+
case .starting, .closed, .http1(.inUse), .http2(.inUse):
126150
throw Errors.connectionNotIdle
127-
}
128151

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

133-
self.isParked = false
155+
case .http1(.idle(true, let lastIdle)):
156+
self.state = .http1(.idle(parked: false, idleSince: lastIdle))
157+
158+
case .http2(.idle(let maxStreams, true, let lastIdle)):
159+
self.state = .http2(.idle(maxConcurrentStreams: maxStreams, parked: false, lastIdle: lastIdle))
160+
}
134161
}
135162

136163
mutating func execute(_ request: HTTPSchedulableRequest) throws {
137-
guard !self.isParked else {
164+
switch self.state {
165+
case .starting, .http1(.inUse):
166+
throw Errors.connectionNotIdle
167+
168+
case .http1(.idle(true, _)), .http2(.idle(_, true, _)):
138169
throw Errors.connectionIsParked
139-
}
140170

141-
if let required = request.requiredEventLoop, required !== self.eventLoop {
142-
throw Errors.connectionDoesNotFulfillEventLoopRequirement
143-
}
171+
case .http1(.idle(false, _)):
172+
if let required = request.requiredEventLoop, required !== self.eventLoop {
173+
throw Errors.connectionDoesNotFulfillEventLoopRequirement
174+
}
175+
self.state = .http1(.inUse)
176+
177+
case .http2(.idle(let maxStreams, false, _)):
178+
if let required = request.requiredEventLoop, required !== self.eventLoop {
179+
throw Errors.connectionDoesNotFulfillEventLoopRequirement
180+
}
181+
if maxStreams < 1 {
182+
throw Errors.connectionDoesNotHaveHTTP2StreamAvailable
183+
}
184+
self.state = .http2(.inUse(maxConcurrentStreams: maxStreams, used: 1))
185+
186+
case .http2(.inUse(let maxStreams, let used)):
187+
if let required = request.requiredEventLoop, required !== self.eventLoop {
188+
throw Errors.connectionDoesNotFulfillEventLoopRequirement
189+
}
190+
if used + 1 > maxStreams {
191+
throw Errors.connectionDoesNotHaveHTTP2StreamAvailable
192+
}
193+
self.state = .http2(.inUse(maxConcurrentStreams: maxStreams, used: used + 1))
144194

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)
157195
case .closed:
158196
throw Errors.connectionIsClosed
159197
}
160198
}
161199

162200
mutating func finishRequest() throws {
163-
guard !self.isParked else {
164-
throw Errors.connectionIsParked
165-
}
166-
167201
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, _):
173-
throw Errors.connectionIsNotExecuting
174-
case .http2(_, let used) where used <= 0:
202+
case .starting, .http1(.idle), .http2(.idle):
175203
throw Errors.connectionIsNotExecuting
176-
case .http2(let streams, var used):
177-
used -= 1
178-
self.state = .http2(streams: streams, used: used)
204+
205+
case .http1(.inUse):
206+
self.state = .http1(.idle(parked: false, idleSince: .now()))
207+
208+
case .http2(.inUse(let maxStreams, let used)):
209+
if used == 1 {
210+
self.state = .http2(.idle(maxConcurrentStreams: maxStreams, parked: false, lastIdle: .now()))
211+
} else {
212+
self.state = .http2(.inUse(maxConcurrentStreams: maxStreams, used: used))
213+
}
214+
179215
case .closed:
180216
throw Errors.connectionIsClosed
181217
}
@@ -185,14 +221,12 @@ struct MockConnectionPool {
185221
switch self.state {
186222
case .starting:
187223
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-
}
224+
case .http1(.idle), .http2(.idle):
225+
self.state = .closed
226+
227+
case .http1(.inUse), .http2(.inUse):
228+
throw Errors.connectionNotIdle
229+
196230
case .closed:
197231
throw Errors.connectionIsClosed
198232
}
@@ -209,7 +243,7 @@ struct MockConnectionPool {
209243
}
210244

211245
var leased: Int {
212-
self.connections.values.filter { $0.isLeased }.count
246+
self.connections.values.filter { $0.isUsed }.count
213247
}
214248

215249
var starting: Int {
@@ -227,22 +261,21 @@ struct MockConnectionPool {
227261
var newestParkedConnection: Connection? {
228262
self.connections.values
229263
.filter { $0.isParked }
230-
.max(by: { $0.lastIdle! < $1.lastIdle! })
264+
.max(by: { $0.idleSince! < $1.idleSince! })
231265
.flatMap { .__testOnly_connection(id: $0.id, eventLoop: $0.eventLoop) }
232266
}
233267

234268
var oldestParkedConnection: Connection? {
235269
self.connections.values
236270
.filter { $0.isParked }
237-
.min(by: { $0.lastIdle! < $1.lastIdle! })
271+
.min(by: { $0.idleSince! < $1.idleSince! })
238272
.flatMap { .__testOnly_connection(id: $0.id, eventLoop: $0.eventLoop) }
239273
}
240274

241275
func newestParkedConnection(for eventLoop: EventLoop) -> Connection? {
242276
self.connections.values
243277
.filter { $0.eventLoop === eventLoop && $0.isParked }
244-
.sorted(by: { $0.lastIdle! > $1.lastIdle! })
245-
.max(by: { $0.lastIdle! < $1.lastIdle! })
278+
.max(by: { $0.idleSince! < $1.idleSince! })
246279
.flatMap { .__testOnly_connection(id: $0.id, eventLoop: $0.eventLoop) }
247280
}
248281

@@ -254,7 +287,7 @@ struct MockConnectionPool {
254287

255288
mutating func createConnection(_ connectionID: Connection.ID, on eventLoop: EventLoop) throws {
256289
guard self.connections[connectionID] == nil else {
257-
throw Errors.connectionIDAlreadyUsed
290+
throw Errors.connectionExists
258291
}
259292
self.connections[connectionID] = .init(id: connectionID, eventLoop: eventLoop)
260293
}
@@ -307,7 +340,7 @@ struct MockConnectionPool {
307340

308341
// MARK: Connection destruction
309342

310-
/// Closing a connection signals intend. For this reason, it is verified, that the connection is not running any
343+
/// Closing a connection signals intent. For this reason, it is verified, that the connection is not running any
311344
/// requests when closing.
312345
mutating func closeConnection(_ connection: Connection) throws {
313346
guard var mockConnection = self.connections.removeValue(forKey: connection.id) else {
@@ -361,7 +394,9 @@ struct MockConnectionPool {
361394
try connection.finishRequest()
362395
self.connections[connectionID] = connection
363396
}
397+
}
364398

399+
extension MockConnectionPool {
365400
mutating func randomStartingConnection() -> Connection.ID? {
366401
self.connections.values
367402
.filter { $0.isStarting }
@@ -371,7 +406,7 @@ struct MockConnectionPool {
371406

372407
mutating func randomActiveConnection() -> Connection.ID? {
373408
self.connections.values
374-
.filter { $0.isLeased || $0.isParked }
409+
.filter { $0.isUsed || $0.isParked }
375410
.randomElement()
376411
.map(\.id)
377412
}
@@ -385,7 +420,7 @@ struct MockConnectionPool {
385420

386421
mutating func randomLeasedConnection() -> Connection? {
387422
self.connections.values
388-
.filter { $0.isLeased }
423+
.filter { $0.isUsed }
389424
.randomElement()
390425
.flatMap { .__testOnly_connection(id: $0.id, eventLoop: $0.eventLoop) }
391426
}

0 commit comments

Comments
 (0)