@@ -85,6 +85,11 @@ internal final class ConnectionPool {
8585 @usableFromInline
8686 internal let maxWaiters : Int
8787
88+ /// The number of connections in the pool that should always be kept open (i.e. they won't go idle).
89+ /// In other words, it's the number of connections for which we should ignore idle timers.
90+ @usableFromInline
91+ internal let minConnections : Int
92+
8893 /// Configuration for backoff between subsequence connection attempts.
8994 @usableFromInline
9095 internal let connectionBackoff : ConnectionBackoff
@@ -157,6 +162,7 @@ internal final class ConnectionPool {
157162 init (
158163 eventLoop: EventLoop ,
159164 maxWaiters: Int ,
165+ minConnections: Int ,
160166 reservationLoadThreshold: Double ,
161167 assumedMaxConcurrentStreams: Int ,
162168 connectionBackoff: ConnectionBackoff ,
@@ -176,6 +182,7 @@ internal final class ConnectionPool {
176182
177183 self . _connections = [ : ]
178184 self . maxWaiters = maxWaiters
185+ self . minConnections = minConnections
179186 self . waiters = CircularBuffer ( initialCapacity: 16 )
180187
181188 self . eventLoop = eventLoop
@@ -201,17 +208,25 @@ internal final class ConnectionPool {
201208 ]
202209 )
203210 self . _connections. reserveCapacity ( connections)
211+ var numberOfKeepOpenConnections = self . minConnections
204212 while self . _connections. count < connections {
205- self . addConnectionToPool ( )
213+ // If we have less than the minimum number of connections, don't let
214+ // the new connection close when idle.
215+ let idleBehavior =
216+ numberOfKeepOpenConnections > 0
217+ ? ConnectionManager . IdleBehavior. neverGoIdle : . closeWhenIdleTimeout
218+ numberOfKeepOpenConnections -= 1
219+ self . addConnectionToPool ( idleBehavior: idleBehavior)
206220 }
207221 }
208222
209223 /// Make and add a new connection to the pool.
210- private func addConnectionToPool( ) {
224+ private func addConnectionToPool( idleBehavior : ConnectionManager . IdleBehavior ) {
211225 let manager = ConnectionManager (
212226 eventLoop: self . eventLoop,
213227 channelProvider: self . channelProvider,
214228 callStartBehavior: . waitsForConnectivity,
229+ idleBehavior: idleBehavior,
215230 connectionBackoff: self . connectionBackoff,
216231 connectivityDelegate: self ,
217232 http2Delegate: self ,
@@ -220,6 +235,19 @@ internal final class ConnectionPool {
220235 let id = manager. id
221236 self . _connections [ id] = PerConnectionState ( manager: manager)
222237 self . delegate? . connectionAdded ( id: . init( id) )
238+
239+ // If it's one of the connections that should be kept open, then connect
240+ // straight away.
241+ switch idleBehavior {
242+ case . neverGoIdle:
243+ self . eventLoop. execute {
244+ if manager. sync. isIdle {
245+ manager. sync. startConnecting ( )
246+ }
247+ }
248+ case . closeWhenIdleTimeout:
249+ ( )
250+ }
223251 }
224252
225253 // MARK: - Called from the pool manager
@@ -689,8 +717,9 @@ extension ConnectionPool: ConnectionManagerConnectivityDelegate {
689717 // Grab the number of reserved streams (before invalidating the index by adding a connection).
690718 let reservedStreams = self . _connections. values [ index] . reservedStreams
691719
692- // Replace the connection with a new idle one.
693- self . addConnectionToPool ( )
720+ // Replace the connection with a new idle one. Keep the idle behavior, so that
721+ // if it's a connection that should be kept alive, we maintain it.
722+ self . addConnectionToPool ( idleBehavior: manager. idleBehavior)
694723
695724 // Since we're removing this connection from the pool (and no new streams can be created on
696725 // the connection), the pool manager can ignore any streams reserved against this connection.
@@ -881,6 +910,22 @@ extension ConnectionPool {
881910 return self . pool. _connections. values. reduce ( 0 ) { $0 &+ ( $1. manager. sync. isIdle ? 1 : 0 ) }
882911 }
883912
913+ /// The number of active (i.e. connecting or ready) connections in the pool.
914+ internal var activeConnections : Int {
915+ self . pool. eventLoop. assertInEventLoop ( )
916+ return self . pool. _connections. values. reduce ( 0 ) {
917+ $0 &+ ( ( $1. manager. sync. isReady || $1. manager. sync. isConnecting) ? 1 : 0 )
918+ }
919+ }
920+
921+ /// The number of connections in the pool in transient failure state.
922+ internal var transientFailureConnections : Int {
923+ self . pool. eventLoop. assertInEventLoop ( )
924+ return self . pool. _connections. values. reduce ( 0 ) {
925+ $0 &+ ( $1. manager. sync. isTransientFailure ? 1 : 0 )
926+ }
927+ }
928+
884929 /// The number of streams currently available to reserve across all connections in the pool.
885930 internal var availableStreams : Int {
886931 self . pool. eventLoop. assertInEventLoop ( )
0 commit comments