@@ -23,8 +23,12 @@ extension HTTPConnectionPool {
23
23
}
24
24
25
25
typealias Action = HTTPConnectionPool . StateMachine . Action
26
+ typealias ConnectionMigrationAction = HTTPConnectionPool . StateMachine . ConnectionMigrationAction
27
+ typealias EstablishedAction = HTTPConnectionPool . StateMachine . EstablishedAction
28
+ typealias EstablishedConnectionAction = HTTPConnectionPool . StateMachine . EstablishedConnectionAction
26
29
27
30
private( set) var connections : HTTP1Connections
31
+ private( set) var http2Connections : HTTP2Connections ?
28
32
private var failedConsecutiveConnectionAttempts : Int = 0
29
33
/// the error from the last connection creation
30
34
private var lastConnectFailure : Error ?
@@ -41,6 +45,73 @@ extension HTTPConnectionPool {
41
45
self . requests = RequestQueue ( )
42
46
}
43
47
48
+ mutating func migrateFromHTTP2(
49
+ http2State: HTTP2StateMachine ,
50
+ newHTTP1Connection: Connection
51
+ ) -> Action {
52
+ self . migrateFromHTTP2 (
53
+ http1Connections: http2State. http1Connections,
54
+ http2Connections: http2State. connections,
55
+ requests: http2State. requests,
56
+ newHTTP1Connection: newHTTP1Connection
57
+ )
58
+ }
59
+
60
+ mutating func migrateFromHTTP2(
61
+ http1Connections: HTTP1Connections ? = nil ,
62
+ http2Connections: HTTP2Connections ,
63
+ requests: RequestQueue ,
64
+ newHTTP1Connection: Connection
65
+ ) -> Action {
66
+ let migrationAction = self . migrateConnectionsAndRequestsFromHTTP2 (
67
+ http1Connections: http1Connections,
68
+ http2Connections: http2Connections,
69
+ requests: requests
70
+ )
71
+
72
+ let newConnectionAction = self . _newHTTP1ConnectionEstablished (
73
+ newHTTP1Connection
74
+ )
75
+
76
+ return . init(
77
+ request: newConnectionAction. request,
78
+ connection: . combined( migrationAction, newConnectionAction. connection)
79
+ )
80
+ }
81
+
82
+ private mutating func migrateConnectionsAndRequestsFromHTTP2(
83
+ http1Connections: HTTP1Connections ? ,
84
+ http2Connections: HTTP2Connections ,
85
+ requests: RequestQueue
86
+ ) -> ConnectionMigrationAction {
87
+ precondition ( self . connections. isEmpty)
88
+ precondition ( self . http2Connections == nil )
89
+ precondition ( self . requests. isEmpty)
90
+
91
+ if let http1Connections = http1Connections {
92
+ self . connections = http1Connections
93
+ }
94
+
95
+ var http2Connections = http2Connections
96
+ let migration = http2Connections. migrateToHTTP1 ( )
97
+ self . connections. migrateFromHTTP2 (
98
+ starting: migration. starting,
99
+ backingOff: migration. backingOff
100
+ )
101
+
102
+ if !http2Connections. isEmpty {
103
+ self . http2Connections = http2Connections
104
+ }
105
+
106
+ // TODO: Close all idle connections from context.close
107
+ // TODO: Start new http1 connections for pending requests
108
+ // TODO: Potentially cancel unneeded bootstraps (Needs cancellable ClientBootstrap)
109
+
110
+ self . requests = requests
111
+
112
+ return . init( closeConnections: [ ] , createConnections: [ ] )
113
+ }
114
+
44
115
// MARK: - Events
45
116
46
117
mutating func executeRequest( _ request: Request ) -> Action {
@@ -137,6 +208,10 @@ extension HTTPConnectionPool {
137
208
}
138
209
139
210
mutating func newHTTP1ConnectionEstablished( _ connection: Connection ) -> Action {
211
+ . init( self . _newHTTP1ConnectionEstablished ( connection) )
212
+ }
213
+
214
+ private mutating func _newHTTP1ConnectionEstablished( _ connection: Connection ) -> EstablishedAction {
140
215
self . failedConsecutiveConnectionAttempts = 0
141
216
self . lastConnectFailure = nil
142
217
let ( index, context) = self . connections. newHTTP1ConnectionEstablished ( connection)
@@ -210,7 +285,7 @@ extension HTTPConnectionPool {
210
285
211
286
mutating func http1ConnectionReleased( _ connectionID: Connection . ID ) -> Action {
212
287
let ( index, context) = self . connections. releaseConnection ( connectionID)
213
- return self . nextActionForIdleConnection ( at: index, context: context)
288
+ return . init ( self . nextActionForIdleConnection ( at: index, context: context) )
214
289
}
215
290
216
291
/// A connection has been unexpectedly closed
@@ -278,7 +353,7 @@ extension HTTPConnectionPool {
278
353
// If there aren't any more connections, everything is shutdown
279
354
let isShutdown : StateMachine . ConnectionAction . IsShutdown
280
355
let unclean = !( cleanupContext. cancel. isEmpty && waitingRequests. isEmpty)
281
- if self . connections. isEmpty {
356
+ if self . connections. isEmpty && self . http2Connections == nil {
282
357
self . state = . shutDown
283
358
isShutdown = . yes( unclean: unclean)
284
359
} else {
@@ -299,7 +374,7 @@ extension HTTPConnectionPool {
299
374
private mutating func nextActionForIdleConnection(
300
375
at index: Int ,
301
376
context: HTTP1Connections . IdleConnectionContext
302
- ) -> Action {
377
+ ) -> EstablishedAction {
303
378
switch self . state {
304
379
case . running:
305
380
switch context. use {
@@ -311,7 +386,7 @@ extension HTTPConnectionPool {
311
386
case . shuttingDown( let unclean) :
312
387
assert ( self . requests. isEmpty)
313
388
let connection = self . connections. closeConnection ( at: index)
314
- if self . connections. isEmpty {
389
+ if self . connections. isEmpty && self . http2Connections == nil {
315
390
return . init(
316
391
request: . none,
317
392
connection: . closeConnection( connection, isShutdown: . yes( unclean: unclean) )
@@ -330,7 +405,7 @@ extension HTTPConnectionPool {
330
405
private mutating func nextActionForIdleGeneralPurposeConnection(
331
406
at index: Int ,
332
407
context: HTTP1Connections . IdleConnectionContext
333
- ) -> Action {
408
+ ) -> EstablishedAction {
334
409
// 1. Check if there are waiting requests in the general purpose queue
335
410
if let request = self . requests. popFirst ( for: nil ) {
336
411
return . init(
@@ -359,7 +434,7 @@ extension HTTPConnectionPool {
359
434
private mutating func nextActionForIdleEventLoopConnection(
360
435
at index: Int ,
361
436
context: HTTP1Connections . IdleConnectionContext
362
- ) -> Action {
437
+ ) -> EstablishedAction {
363
438
// Check if there are waiting requests in the matching eventLoop queue
364
439
if let request = self . requests. popFirst ( for: context. eventLoop) {
365
440
return . init(
@@ -398,7 +473,7 @@ extension HTTPConnectionPool {
398
473
case . shuttingDown( let unclean) :
399
474
assert ( self . requests. isEmpty)
400
475
self . connections. removeConnection ( at: index)
401
- if self . connections. isEmpty {
476
+ if self . connections. isEmpty && self . http2Connections == nil {
402
477
return . init(
403
478
request: . none,
404
479
connection: . cleanupConnections( . init( ) , isShutdown: . yes( unclean: unclean) )
@@ -444,6 +519,99 @@ extension HTTPConnectionPool {
444
519
self . connections. removeConnection ( at: index)
445
520
return . none
446
521
}
522
+
523
+ // MARK: HTTP2
524
+
525
+ mutating func newHTTP2MaxConcurrentStreamsReceived( _ connectionID: Connection . ID , newMaxStreams: Int ) -> Action {
526
+ // It is save to bang the http2Connections here. If we get this callback but we don't have
527
+ // http2 connections something has gone terribly wrong.
528
+ _ = self . http2Connections!. newHTTP2MaxConcurrentStreamsReceived ( connectionID, newMaxStreams: newMaxStreams)
529
+ return . none
530
+ }
531
+
532
+ mutating func http2ConnectionGoAwayReceived( _ connectionID: Connection . ID ) -> Action {
533
+ // It is save to bang the http2Connections here. If we get this callback but we don't have
534
+ // http2 connections something has gone terribly wrong.
535
+ _ = self . http2Connections!. goAwayReceived ( connectionID)
536
+ return . none
537
+ }
538
+
539
+ mutating func http2ConnectionClosed( _ connectionID: Connection . ID ) -> Action {
540
+ switch self . state {
541
+ case . running:
542
+ _ = self . http2Connections? . failConnection ( connectionID)
543
+ if self . http2Connections? . isEmpty == true {
544
+ self . http2Connections = nil
545
+ }
546
+ return . none
547
+
548
+ case . shuttingDown( let unclean) :
549
+ assert ( self . requests. isEmpty)
550
+ _ = self . http2Connections? . failConnection ( connectionID)
551
+ if self . http2Connections? . isEmpty == true {
552
+ self . http2Connections = nil
553
+ }
554
+ if self . connections. isEmpty && self . http2Connections == nil {
555
+ return . init(
556
+ request: . none,
557
+ connection: . cleanupConnections( . init( ) , isShutdown: . yes( unclean: unclean) )
558
+ )
559
+ }
560
+ return . init(
561
+ request: . none,
562
+ connection: . none
563
+ )
564
+
565
+ case . shutDown:
566
+ preconditionFailure ( " It the pool is already shutdown, all connections must have been torn down. " )
567
+ }
568
+ }
569
+
570
+ mutating func http2ConnectionStreamClosed( _ connectionID: Connection . ID ) -> Action {
571
+ // It is save to bang the http2Connections here. If we get this callback but we don't have
572
+ // http2 connections something has gone terribly wrong.
573
+ switch self . state {
574
+ case . running:
575
+ let ( index, context) = self . http2Connections!. releaseStream ( connectionID)
576
+ guard context. isIdle else {
577
+ return . none
578
+ }
579
+
580
+ let connection = self . http2Connections!. closeConnection ( at: index)
581
+ if self . http2Connections!. isEmpty {
582
+ self . http2Connections = nil
583
+ }
584
+ return . init(
585
+ request: . none,
586
+ connection: . closeConnection( connection, isShutdown: . no)
587
+ )
588
+
589
+ case . shuttingDown( let unclean) :
590
+ assert ( self . requests. isEmpty)
591
+ let ( index, context) = self . http2Connections!. releaseStream ( connectionID)
592
+ guard context. isIdle else {
593
+ return . none
594
+ }
595
+
596
+ let connection = self . http2Connections!. closeConnection ( at: index)
597
+ if self . http2Connections!. isEmpty {
598
+ self . http2Connections = nil
599
+ }
600
+ if self . connections. isEmpty && self . http2Connections == nil {
601
+ return . init(
602
+ request: . none,
603
+ connection: . closeConnection( connection, isShutdown: . yes( unclean: unclean) )
604
+ )
605
+ }
606
+ return . init(
607
+ request: . none,
608
+ connection: . closeConnection( connection, isShutdown: . no)
609
+ )
610
+
611
+ case . shutDown:
612
+ preconditionFailure ( " It the pool is already shutdown, all connections must have been torn down. " )
613
+ }
614
+ }
447
615
}
448
616
}
449
617
0 commit comments