@@ -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,46 @@ 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 idle and did we create a timeout timer for it?
92
+ var isParked : Bool {
83
93
switch self . state {
84
- case . starting, . closed:
94
+ case . starting, . closed, . http1 ( . inUse ) , . http2 ( . 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 . http1( . idle( let parked, _) ) , . http2( . idle( _, let parked, _) ) :
98
+ return parked
90
99
}
91
100
}
92
101
93
- var lastIdle : NIODeadline ? {
102
+ /// Is the connection in use (are there requests executing on it)
103
+ var isUsed : Bool {
94
104
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) :
100
116
return nil
117
+
118
+ case . http1( . idle( _, let lastIdle) ) , . http2( . idle( _, _, let lastIdle) ) :
119
+ return lastIdle
101
120
}
102
121
}
103
122
@@ -106,76 +125,93 @@ struct MockConnectionPool {
106
125
throw Errors . connectionIsNotStarting
107
126
}
108
127
109
- self . state = . http1( leased : false , lastIdle : . now( ) )
128
+ self . state = . http1( . idle ( parked : false , idleSince : . now( ) ) )
110
129
}
111
130
112
131
mutating func park( ) throws {
113
- guard self . isIdle else {
132
+ switch self . state {
133
+ case . starting, . closed, . http1( . inUse) , . http2( . inUse) :
114
134
throw Errors . connectionNotIdle
115
- }
116
135
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) )
120
141
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
+ }
122
145
}
123
146
124
147
mutating func activate( ) throws {
125
- guard self . isIdle else {
148
+ switch self . state {
149
+ case . starting, . closed, . http1( . inUse) , . http2( . inUse) :
126
150
throw Errors . connectionNotIdle
127
- }
128
151
129
- guard self . isParked else {
152
+ case . http1 ( . idle ( false , _ ) ) , . http2 ( . idle ( _ , false , _ ) ) :
130
153
throw Errors . connectionNotParked
131
- }
132
154
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
+ }
134
161
}
135
162
136
163
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 , _) ) :
138
169
throw Errors . connectionIsParked
139
- }
140
170
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 ) )
144
194
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
195
case . closed:
158
196
throw Errors . connectionIsClosed
159
197
}
160
198
}
161
199
162
200
mutating func finishRequest( ) throws {
163
- guard !self . isParked else {
164
- throw Errors . connectionIsParked
165
- }
166
-
167
201
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) :
175
203
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
+
179
215
case . closed:
180
216
throw Errors . connectionIsClosed
181
217
}
@@ -185,14 +221,12 @@ struct MockConnectionPool {
185
221
switch self . state {
186
222
case . starting:
187
223
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
+
196
230
case . closed:
197
231
throw Errors . connectionIsClosed
198
232
}
@@ -209,7 +243,7 @@ struct MockConnectionPool {
209
243
}
210
244
211
245
var leased : Int {
212
- self . connections. values. filter { $0. isLeased } . count
246
+ self . connections. values. filter { $0. isUsed } . count
213
247
}
214
248
215
249
var starting : Int {
@@ -227,22 +261,21 @@ struct MockConnectionPool {
227
261
var newestParkedConnection : Connection ? {
228
262
self . connections. values
229
263
. filter { $0. isParked }
230
- . max ( by: { $0. lastIdle ! < $1. lastIdle ! } )
264
+ . max ( by: { $0. idleSince ! < $1. idleSince ! } )
231
265
. flatMap { . __testOnly_connection( id: $0. id, eventLoop: $0. eventLoop) }
232
266
}
233
267
234
268
var oldestParkedConnection : Connection ? {
235
269
self . connections. values
236
270
. filter { $0. isParked }
237
- . min ( by: { $0. lastIdle ! < $1. lastIdle ! } )
271
+ . min ( by: { $0. idleSince ! < $1. idleSince ! } )
238
272
. flatMap { . __testOnly_connection( id: $0. id, eventLoop: $0. eventLoop) }
239
273
}
240
274
241
275
func newestParkedConnection( for eventLoop: EventLoop ) -> Connection ? {
242
276
self . connections. values
243
277
. 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! } )
246
279
. flatMap { . __testOnly_connection( id: $0. id, eventLoop: $0. eventLoop) }
247
280
}
248
281
@@ -254,7 +287,7 @@ struct MockConnectionPool {
254
287
255
288
mutating func createConnection( _ connectionID: Connection . ID , on eventLoop: EventLoop ) throws {
256
289
guard self . connections [ connectionID] == nil else {
257
- throw Errors . connectionIDAlreadyUsed
290
+ throw Errors . connectionExists
258
291
}
259
292
self . connections [ connectionID] = . init( id: connectionID, eventLoop: eventLoop)
260
293
}
@@ -307,7 +340,7 @@ struct MockConnectionPool {
307
340
308
341
// MARK: Connection destruction
309
342
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
311
344
/// requests when closing.
312
345
mutating func closeConnection( _ connection: Connection ) throws {
313
346
guard var mockConnection = self . connections. removeValue ( forKey: connection. id) else {
@@ -361,7 +394,9 @@ struct MockConnectionPool {
361
394
try connection. finishRequest ( )
362
395
self . connections [ connectionID] = connection
363
396
}
397
+ }
364
398
399
+ extension MockConnectionPool {
365
400
mutating func randomStartingConnection( ) -> Connection . ID ? {
366
401
self . connections. values
367
402
. filter { $0. isStarting }
@@ -371,7 +406,7 @@ struct MockConnectionPool {
371
406
372
407
mutating func randomActiveConnection( ) -> Connection . ID ? {
373
408
self . connections. values
374
- . filter { $0. isLeased || $0. isParked }
409
+ . filter { $0. isUsed || $0. isParked }
375
410
. randomElement ( )
376
411
. map ( \. id)
377
412
}
@@ -385,7 +420,7 @@ struct MockConnectionPool {
385
420
386
421
mutating func randomLeasedConnection( ) -> Connection ? {
387
422
self . connections. values
388
- . filter { $0. isLeased }
423
+ . filter { $0. isUsed }
389
424
. randomElement ( )
390
425
. flatMap { . __testOnly_connection( id: $0. id, eventLoop: $0. eventLoop) }
391
426
}
0 commit comments