@@ -22,37 +22,46 @@ import NIOHTTP1
22
22
struct MockConnectionPool {
23
23
typealias Connection = HTTPConnectionPool . Connection
24
24
25
- enum Errors : Error {
25
+ enum Errors : Error , Hashable {
26
26
case connectionIDAlreadyUsed
27
27
case connectionNotFound
28
28
case connectionExists
29
29
case connectionNotIdle
30
- case connectionAlreadyParked
31
30
case connectionNotParked
32
31
case connectionIsParked
33
32
case connectionIsClosed
34
33
case connectionIsNotStarting
35
34
case connectionIsNotExecuting
36
35
case connectionDoesNotFulfillEventLoopRequirement
36
+ case connectionDoesNotHaveHTTP2StreamAvailable
37
37
case connectionBackoffTimerExists
38
38
case connectionBackoffTimerNotFound
39
39
}
40
40
41
- private struct MockConnectionState {
41
+ fileprivate struct MockConnectionState {
42
42
typealias ID = HTTPConnectionPool . Connection . ID
43
43
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
+
45
55
case starting
46
- case http1( leased : Bool , lastIdle : NIODeadline )
47
- case http2( streams : Int , used : Int )
56
+ case http1( HTTP1State )
57
+ case http2( HTTP2State )
48
58
case closed
49
59
}
50
60
51
61
let id : ID
52
62
let eventLoop : EventLoop
53
63
54
- private( set) var state : State = . starting
55
- private( set) var isParked : Bool = false
64
+ private var state : State = . starting
56
65
57
66
init ( id: ID , eventLoop: EventLoop ) {
58
67
self . id = id
@@ -68,36 +77,60 @@ struct MockConnectionPool {
68
77
}
69
78
}
70
79
80
+ /// Is the connection idle (meaning there are no requests executing on it)
71
81
var isIdle : Bool {
72
82
switch self . state {
73
- case . starting, . closed:
83
+ case . starting, . closed, . http1 ( . inUse ) , . http2 ( . inUse ) :
74
84
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
79
88
}
80
89
}
81
90
82
- var isLeased : Bool {
91
+ /// Is the connection available (can another request be executed on it)
92
+ var isAvailable : Bool {
83
93
switch self . state {
84
- case . starting, . closed:
94
+ case . starting, . closed, . http1 ( . inUse ) :
85
95
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
90
102
}
91
103
}
92
104
93
- var lastIdle : NIODeadline ? {
105
+ /// Is the connection idle and did we create a timeout timer for it?
106
+ var isParked : Bool {
94
107
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) :
100
130
return nil
131
+
132
+ case . http1( . idle( _, let lastIdle) ) , . http2( . idle( _, _, let lastIdle) ) :
133
+ return lastIdle
101
134
}
102
135
}
103
136
@@ -106,76 +139,93 @@ struct MockConnectionPool {
106
139
throw Errors . connectionIsNotStarting
107
140
}
108
141
109
- self . state = . http1( leased : false , lastIdle : . now( ) )
142
+ self . state = . http1( . idle ( parked : false , idleSince : . now( ) ) )
110
143
}
111
144
112
145
mutating func park( ) throws {
113
- guard self . isIdle else {
146
+ switch self . state {
147
+ case . starting, . closed, . http1( . inUse) , . http2( . inUse) :
114
148
throw Errors . connectionNotIdle
115
- }
116
149
117
- guard !self . isParked else {
118
- throw Errors . connectionAlreadyParked
119
- }
150
+ case . http1( . idle( true , _) ) , . http2( . idle( _, true , _) ) :
151
+ throw Errors . connectionIsParked
120
152
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
+ }
122
159
}
123
160
124
161
mutating func activate( ) throws {
125
- guard self . isIdle else {
162
+ switch self . state {
163
+ case . starting, . closed, . http1( . inUse) , . http2( . inUse) :
126
164
throw Errors . connectionNotIdle
127
- }
128
165
129
- guard self . isParked else {
166
+ case . http1 ( . idle ( false , _ ) ) , . http2 ( . idle ( _ , false , _ ) ) :
130
167
throw Errors . connectionNotParked
131
- }
132
168
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
+ }
134
175
}
135
176
136
177
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 , _) ) :
138
183
throw Errors . connectionIsParked
139
- }
140
184
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 ) )
144
208
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)
157
209
case . closed:
158
210
throw Errors . connectionIsClosed
159
211
}
160
212
}
161
213
162
214
mutating func finishRequest( ) throws {
163
- guard !self . isParked else {
164
- throw Errors . connectionIsParked
165
- }
166
-
167
215
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) :
173
217
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
+
179
229
case . closed:
180
230
throw Errors . connectionIsClosed
181
231
}
@@ -185,14 +235,12 @@ struct MockConnectionPool {
185
235
switch self . state {
186
236
case . starting:
187
237
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
+
196
244
case . closed:
197
245
throw Errors . connectionIsClosed
198
246
}
@@ -209,7 +257,7 @@ struct MockConnectionPool {
209
257
}
210
258
211
259
var leased : Int {
212
- self . connections. values. filter { $0. isLeased } . count
260
+ self . connections. values. filter { $0. isUsed } . count
213
261
}
214
262
215
263
var starting : Int {
@@ -227,22 +275,21 @@ struct MockConnectionPool {
227
275
var newestParkedConnection : Connection ? {
228
276
self . connections. values
229
277
. filter { $0. isParked }
230
- . max ( by: { $0. lastIdle ! < $1. lastIdle ! } )
278
+ . max ( by: { $0. idleSince ! < $1. idleSince ! } )
231
279
. flatMap { . __testOnly_connection( id: $0. id, eventLoop: $0. eventLoop) }
232
280
}
233
281
234
282
var oldestParkedConnection : Connection ? {
235
283
self . connections. values
236
284
. filter { $0. isParked }
237
- . min ( by: { $0. lastIdle ! < $1. lastIdle ! } )
285
+ . min ( by: { $0. idleSince ! < $1. idleSince ! } )
238
286
. flatMap { . __testOnly_connection( id: $0. id, eventLoop: $0. eventLoop) }
239
287
}
240
288
241
289
func newestParkedConnection( for eventLoop: EventLoop ) -> Connection ? {
242
290
self . connections. values
243
291
. 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! } )
246
293
. flatMap { . __testOnly_connection( id: $0. id, eventLoop: $0. eventLoop) }
247
294
}
248
295
@@ -254,7 +301,7 @@ struct MockConnectionPool {
254
301
255
302
mutating func createConnection( _ connectionID: Connection . ID , on eventLoop: EventLoop ) throws {
256
303
guard self . connections [ connectionID] == nil else {
257
- throw Errors . connectionIDAlreadyUsed
304
+ throw Errors . connectionExists
258
305
}
259
306
self . connections [ connectionID] = . init( id: connectionID, eventLoop: eventLoop)
260
307
}
@@ -307,7 +354,7 @@ struct MockConnectionPool {
307
354
308
355
// MARK: Connection destruction
309
356
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
311
358
/// requests when closing.
312
359
mutating func closeConnection( _ connection: Connection ) throws {
313
360
guard var mockConnection = self . connections. removeValue ( forKey: connection. id) else {
@@ -361,7 +408,9 @@ struct MockConnectionPool {
361
408
try connection. finishRequest ( )
362
409
self . connections [ connectionID] = connection
363
410
}
411
+ }
364
412
413
+ extension MockConnectionPool {
365
414
mutating func randomStartingConnection( ) -> Connection . ID ? {
366
415
self . connections. values
367
416
. filter { $0. isStarting }
@@ -371,7 +420,7 @@ struct MockConnectionPool {
371
420
372
421
mutating func randomActiveConnection( ) -> Connection . ID ? {
373
422
self . connections. values
374
- . filter { $0. isLeased || $0. isParked }
423
+ . filter { $0. isUsed || $0. isParked }
375
424
. randomElement ( )
376
425
. map ( \. id)
377
426
}
@@ -385,7 +434,7 @@ struct MockConnectionPool {
385
434
386
435
mutating func randomLeasedConnection( ) -> Connection ? {
387
436
self . connections. values
388
- . filter { $0. isLeased }
437
+ . filter { $0. isUsed }
389
438
. randomElement ( )
390
439
. flatMap { . __testOnly_connection( id: $0. id, eventLoop: $0. eventLoop) }
391
440
}
0 commit comments