@@ -24,15 +24,17 @@ protocol HTTPConnectionPoolDelegate {
24
24
final class HTTPConnectionPool {
25
25
private let stateLock = Lock ( )
26
26
private var _state : StateMachine
27
+ /// The connection idle timeout timers. Protected by the stateLock
28
+ private var _idleTimer = [ Connection . ID : Scheduled < Void > ] ( )
29
+ /// The connection backoff timeout timers. Protected by the stateLock
30
+ private var _backoffTimer = [ Connection . ID : Scheduled < Void > ] ( )
27
31
28
32
private static let fallbackConnectTimeout : TimeAmount = . seconds( 30 )
29
33
30
34
let key : ConnectionPool . Key
31
35
32
36
private let timerLock = Lock ( )
33
37
private var _requestTimer = [ Request . ID : Scheduled < Void > ] ( )
34
- private var _idleTimer = [ Connection . ID : Scheduled < Void > ] ( )
35
- private var _backoffTimer = [ Connection . ID : Scheduled < Void > ] ( )
36
38
37
39
private var logger : Logger
38
40
@@ -75,32 +77,89 @@ final class HTTPConnectionPool {
75
77
}
76
78
77
79
func executeRequest( _ request: HTTPSchedulableRequest ) {
78
- let action = self . stateLock. withLock { ( ) -> StateMachine . Action in
79
- self . _state. executeRequest ( . init( request) )
80
- }
81
- self . run ( action: action)
80
+ self . modifyStateAndRunActions { $0. executeRequest ( . init( request) ) }
82
81
}
83
82
84
83
func shutdown( ) {
85
- let action = self . stateLock. withLock { ( ) -> StateMachine . Action in
86
- self . _state. shutdown ( )
84
+ self . modifyStateAndRunActions { $0. shutdown ( ) }
85
+ }
86
+
87
+ // MARK: - Private Methods -
88
+
89
+ // MARK: Actions
90
+
91
+ ///
92
+ private struct Actions {
93
+ enum ConnectionAction {
94
+ enum Unlocked {
95
+ case createConnection( Connection . ID , on: EventLoop )
96
+ case closeConnection( Connection , isShutdown: StateMachine . ConnectionAction . IsShutdown )
97
+ case cleanupConnections( CleanupContext , isShutdown: StateMachine . ConnectionAction . IsShutdown )
98
+ case none
99
+ }
100
+
101
+ enum Locked {
102
+ case scheduleBackoffTimer( Connection . ID , backoff: TimeAmount , on: EventLoop )
103
+ case cancelBackoffTimers( [ Connection . ID ] )
104
+ case scheduleTimeoutTimer( Connection . ID , on: EventLoop )
105
+ case cancelTimeoutTimer( Connection . ID )
106
+ case none
107
+ }
108
+ }
109
+
110
+ struct Locked {
111
+ var connection : ConnectionAction . Locked
112
+ }
113
+
114
+ struct Unlocked {
115
+ var connection : ConnectionAction . Unlocked
116
+ var request : StateMachine . RequestAction
117
+ }
118
+
119
+ var locked : Locked
120
+ var unlocked : Unlocked
121
+
122
+ init ( from stateMachineAction: StateMachine . Action ) {
123
+ self . locked = Locked ( connection: . none)
124
+ self . unlocked = Unlocked ( connection: . none, request: stateMachineAction. request)
125
+
126
+ switch stateMachineAction. connection {
127
+ case . createConnection( let connectionID, on: let eventLoop) :
128
+ self . unlocked. connection = . createConnection( connectionID, on: eventLoop)
129
+ case . scheduleBackoffTimer( let connectionID, backoff: let backoff, on: let eventLoop) :
130
+ self . locked. connection = . scheduleBackoffTimer( connectionID, backoff: backoff, on: eventLoop)
131
+ case . scheduleTimeoutTimer( let connectionID, on: let eventLoop) :
132
+ self . locked. connection = . scheduleTimeoutTimer( connectionID, on: eventLoop)
133
+ case . cancelTimeoutTimer( let connectionID) :
134
+ self . locked. connection = . cancelTimeoutTimer( connectionID)
135
+ case . closeConnection( let connection, isShutdown: let isShutdown) :
136
+ self . unlocked. connection = . closeConnection( connection, isShutdown: isShutdown)
137
+ case . cleanupConnections( var cleanupContext, isShutdown: let isShutdown) :
138
+ //
139
+ self . locked. connection = . cancelBackoffTimers( cleanupContext. connectBackoff)
140
+ cleanupContext. connectBackoff = [ ]
141
+ self . unlocked. connection = . cleanupConnections( cleanupContext, isShutdown: isShutdown)
142
+ case . none:
143
+ break
144
+ }
87
145
}
88
- self . run ( action: action)
89
146
}
90
147
91
148
// MARK: Run actions
92
149
93
- private func run( action: StateMachine . Action ) {
94
- self . runConnectionAction ( action. connection)
95
- self . runRequestAction ( action. request)
150
+ private func modifyStateAndRunActions( _ closure: ( inout StateMachine ) -> StateMachine . Action ) {
151
+ let unlockedActions = self . stateLock. withLock { ( ) -> Actions . Unlocked in
152
+ let stateMachineAction = closure ( & self . _state)
153
+ let poolAction = Actions ( from: stateMachineAction)
154
+ self . runLockedActions ( poolAction. locked)
155
+ return poolAction. unlocked
156
+ }
157
+ self . runUnlockedActions ( unlockedActions)
96
158
}
97
159
98
- private func runConnectionAction( _ action: StateMachine . ConnectionAction ) {
99
- switch action {
100
- case . createConnection( let connectionID, let eventLoop) :
101
- self . createConnection ( connectionID, on: eventLoop)
102
-
103
- case . scheduleBackoffTimer( let connectionID, let backoff, on: let eventLoop) :
160
+ private func runLockedActions( _ actions: Actions . Locked ) {
161
+ switch actions. connection {
162
+ case . scheduleBackoffTimer( let connectionID, backoff: let backoff, on: let eventLoop) :
104
163
self . scheduleConnectionStartBackoffTimer ( connectionID, backoff, on: eventLoop)
105
164
106
165
case . scheduleTimeoutTimer( let connectionID, on: let eventLoop) :
@@ -109,6 +168,26 @@ final class HTTPConnectionPool {
109
168
case . cancelTimeoutTimer( let connectionID) :
110
169
self . cancelIdleTimerForConnection ( connectionID)
111
170
171
+ case . cancelBackoffTimers( let connectionIDs) :
172
+ for connectionID in connectionIDs {
173
+ self . cancelConnectionStartBackoffTimer ( connectionID)
174
+ }
175
+
176
+ case . none:
177
+ break
178
+ }
179
+ }
180
+
181
+ private func runUnlockedActions( _ actions: Actions . Unlocked ) {
182
+ self . runUnlockedConnectionAction ( actions. connection)
183
+ self . runUnlockedRequestAction ( actions. request)
184
+ }
185
+
186
+ private func runUnlockedConnectionAction( _ action: Actions . ConnectionAction . Unlocked ) {
187
+ switch action {
188
+ case . createConnection( let connectionID, let eventLoop) :
189
+ self . createConnection ( connectionID, on: eventLoop)
190
+
112
191
case . closeConnection( let connection, isShutdown: let isShutdown) :
113
192
self . logger. trace ( " close connection " , metadata: [
114
193
" ahc-connection-id " : " \( connection. id) " ,
@@ -143,7 +222,7 @@ final class HTTPConnectionPool {
143
222
}
144
223
}
145
224
146
- private func runRequestAction ( _ action: StateMachine . RequestAction ) {
225
+ private func runUnlockedRequestAction ( _ action: StateMachine . RequestAction ) {
147
226
// The order of execution fail/execute request vs cancelling the request timeout timer does
148
227
// not matter in the actions here. The actions don't cause any side effects that will be
149
228
// reported back to the state machine and are not dependent on each other.
@@ -215,11 +294,9 @@ final class HTTPConnectionPool {
215
294
guard timeoutFired else { return }
216
295
217
296
// 3. Tell the state machine about the timeout
218
- let action = self . stateLock . withLock {
219
- self . _state . timeoutRequest ( requestID)
297
+ self . modifyStateAndRunActions {
298
+ $0 . timeoutRequest ( requestID)
220
299
}
221
-
222
- self . run ( action: action)
223
300
}
224
301
225
302
self . timerLock. withLockVoid {
@@ -254,34 +331,27 @@ final class HTTPConnectionPool {
254
331
let scheduled = eventLoop. scheduleTask ( in: self . idleConnectionTimeout) {
255
332
// there might be a race between a cancelTimer call and the triggering
256
333
// of this scheduled task. both want to acquire the lock
257
- let timerExisted = self . timerLock. withLock {
258
- self . _idleTimer. removeValue ( forKey: connectionID) != nil
334
+ self . modifyStateAndRunActions { stateMachine in
335
+ if self . _idleTimer. removeValue ( forKey: connectionID) != nil {
336
+ // The timer still exists. State Machines assumes it is alive
337
+ return stateMachine. connectionIdleTimeout ( connectionID)
338
+ }
339
+ return . none
259
340
}
260
-
261
- guard timerExisted else { return }
262
-
263
- let action = self . stateLock. withLock {
264
- self . _state. connectionIdleTimeout ( connectionID)
265
- }
266
- self . run ( action: action)
267
341
}
268
342
269
- self . timerLock. withLock {
270
- assert ( self . _idleTimer [ connectionID] == nil )
271
- self . _idleTimer [ connectionID] = scheduled
272
- }
343
+ assert ( self . _idleTimer [ connectionID] == nil )
344
+ self . _idleTimer [ connectionID] = scheduled
273
345
}
274
346
275
347
private func cancelIdleTimerForConnection( _ connectionID: Connection . ID ) {
276
348
self . logger. trace ( " Cancel idle connection timeout timer " , metadata: [
277
349
" ahc-connection-id " : " \( connectionID) " ,
278
350
] )
279
-
280
- let cancelTimer = self . timerLock. withLock {
281
- self . _idleTimer. removeValue ( forKey: connectionID)
351
+ guard let cancelTimer = self . _idleTimer. removeValue ( forKey: connectionID) else {
352
+ preconditionFailure ( " Expected to have an idle timer for connection \( connectionID) at this point. " )
282
353
}
283
-
284
- cancelTimer? . cancel ( )
354
+ cancelTimer. cancel ( )
285
355
}
286
356
287
357
private func scheduleConnectionStartBackoffTimer(
@@ -295,30 +365,24 @@ final class HTTPConnectionPool {
295
365
296
366
let scheduled = eventLoop. scheduleTask ( in: timeAmount) {
297
367
// there might be a race between a backoffTimer and the pool shutting down.
298
- let timerExisted = self . timerLock. withLock {
299
- self . _backoffTimer. removeValue ( forKey: connectionID) != nil
300
- }
301
-
302
- guard timerExisted else { return }
303
-
304
- let action = self . stateLock. withLock {
305
- self . _state. connectionCreationBackoffDone ( connectionID)
368
+ self . modifyStateAndRunActions { stateMachine in
369
+ if self . _backoffTimer. removeValue ( forKey: connectionID) != nil {
370
+ // The timer still exists. State Machines assumes it is alive
371
+ return stateMachine. connectionCreationBackoffDone ( connectionID)
372
+ }
373
+ return . none
306
374
}
307
- self . run ( action: action)
308
375
}
309
376
310
- self . timerLock. withLock {
311
- assert ( self . _backoffTimer [ connectionID] == nil )
312
- self . _backoffTimer [ connectionID] = scheduled
313
- }
377
+ assert ( self . _backoffTimer [ connectionID] == nil )
378
+ self . _backoffTimer [ connectionID] = scheduled
314
379
}
315
380
316
381
private func cancelConnectionStartBackoffTimer( _ connectionID: Connection . ID ) {
317
- let backoffTimer = self . timerLock . withLock {
318
- self . _backoffTimer [ connectionID]
382
+ guard let backoffTimer = self . _backoffTimer . removeValue ( forKey : connectionID ) else {
383
+ preconditionFailure ( " Expected to have a backoff timer for connection \( connectionID) at this point. " )
319
384
}
320
-
321
- backoffTimer? . cancel ( )
385
+ backoffTimer. cancel ( )
322
386
}
323
387
}
324
388
@@ -330,10 +394,9 @@ extension HTTPConnectionPool: HTTPConnectionRequester {
330
394
" ahc-connection-id " : " \( connection. id) " ,
331
395
" ahc-http-version " : " http/1.1 " ,
332
396
] )
333
- let action = self . stateLock . withLock {
334
- self . _state . newHTTP1ConnectionCreated ( . http1_1( connection) )
397
+ self . modifyStateAndRunActions {
398
+ $0 . newHTTP1ConnectionCreated ( . http1_1( connection) )
335
399
}
336
- self . run ( action: action)
337
400
}
338
401
339
402
func http2ConnectionCreated( _ connection: HTTP2Connection , maximumStreams: Int ) {
@@ -356,10 +419,9 @@ extension HTTPConnectionPool: HTTPConnectionRequester {
356
419
" ahc-error " : " \( error) " ,
357
420
" ahc-connection-id " : " \( connectionID) " ,
358
421
] )
359
- let action = self . stateLock . withLock {
360
- self . _state . failedToCreateNewConnection ( error, connectionID: connectionID)
422
+ self . modifyStateAndRunActions {
423
+ $0 . failedToCreateNewConnection ( error, connectionID: connectionID)
361
424
}
362
- self . run ( action: action)
363
425
}
364
426
}
365
427
@@ -369,21 +431,19 @@ extension HTTPConnectionPool: HTTP1ConnectionDelegate {
369
431
" ahc-connection-id " : " \( connection. id) " ,
370
432
" ahc-http-version " : " http/1.1 " ,
371
433
] )
372
- let action = self . stateLock . withLock {
373
- self . _state . connectionClosed ( connection. id)
434
+ self . modifyStateAndRunActions {
435
+ $0 . connectionClosed ( connection. id)
374
436
}
375
- self . run ( action: action)
376
437
}
377
438
378
439
func http1ConnectionReleased( _ connection: HTTP1Connection ) {
379
440
self . logger. trace ( " releasing connection " , metadata: [
380
441
" ahc-connection-id " : " \( connection. id) " ,
381
442
" ahc-http-version " : " http/1.1 " ,
382
443
] )
383
- let action = self . stateLock . withLock {
384
- self . _state . http1ConnectionReleased ( connection. id)
444
+ self . modifyStateAndRunActions {
445
+ $0 . http1ConnectionReleased ( connection. id)
385
446
}
386
- self . run ( action: action)
387
447
}
388
448
}
389
449
@@ -416,10 +476,9 @@ extension HTTPConnectionPool: HTTP2ConnectionDelegate {
416
476
extension HTTPConnectionPool : HTTPRequestScheduler {
417
477
func cancelRequest( _ request: HTTPSchedulableRequest ) {
418
478
let requestID = Request ( request) . id
419
- let action = self . stateLock . withLock {
420
- self . _state . cancelRequest ( requestID)
479
+ self . modifyStateAndRunActions {
480
+ $0 . cancelRequest ( requestID)
421
481
}
422
- self . run ( action: action)
423
482
}
424
483
}
425
484
0 commit comments