@@ -257,6 +257,30 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup(
257
257
258
258
return connectionPoolGroup ;
259
259
}
260
+
261
+ internal DbMetaDataFactory GetMetaDataFactory (
262
+ DbConnectionPoolGroup poolGroup ,
263
+ DbConnectionInternal internalConnection )
264
+ {
265
+ Debug . Assert ( poolGroup is not null , "connectionPoolGroup may not be null." ) ;
266
+
267
+ // Get the matadatafactory from the pool entry. If it does not already have one
268
+ // create one and save it on the pool entry
269
+ DbMetaDataFactory metaDataFactory = poolGroup . MetaDataFactory ;
270
+
271
+ // CONSIDER: serializing this so we don't construct multiple metadata factories
272
+ // if two threads happen to hit this at the same time. One will be GC'd
273
+ if ( metaDataFactory is null )
274
+ {
275
+ metaDataFactory = CreateMetaDataFactory ( internalConnection , out bool allowCache ) ;
276
+ if ( allowCache )
277
+ {
278
+ poolGroup . MetaDataFactory = metaDataFactory ;
279
+ }
280
+ }
281
+
282
+ return metaDataFactory ;
283
+ }
260
284
261
285
internal void QueuePoolForRelease ( IDbConnectionPool pool , bool clearing )
262
286
{
@@ -801,8 +825,94 @@ private IDbConnectionPool GetConnectionPool(
801
825
IDbConnectionPool connectionPool = connectionPoolGroup . GetConnectionPool ( this ) ;
802
826
return connectionPool ;
803
827
}
804
-
805
-
828
+
829
+ private void PruneConnectionPoolGroups ( object state )
830
+ {
831
+ // When debugging this method, expect multiple threads at the same time
832
+ SqlClientEventSource . Log . TryAdvancedTraceEvent ( "<prov.SqlConnectionFactory.PruneConnectionPoolGroups|RES|INFO|CPOOL> {0}" , ObjectId ) ;
833
+
834
+ // First, walk the pool release list and attempt to clear each pool, when the pool is
835
+ // finally empty, we dispose of it. If the pool isn't empty, it's because there are
836
+ // active connections or distributed transactions that need it.
837
+ lock ( _poolsToRelease )
838
+ {
839
+ if ( _poolsToRelease . Count != 0 )
840
+ {
841
+ IDbConnectionPool [ ] poolsToRelease = _poolsToRelease . ToArray ( ) ;
842
+ foreach ( IDbConnectionPool pool in poolsToRelease )
843
+ {
844
+ if ( pool is not null )
845
+ {
846
+ pool . Clear ( ) ;
847
+
848
+ if ( pool . Count == 0 )
849
+ {
850
+ _poolsToRelease . Remove ( pool ) ;
851
+
852
+ SqlClientEventSource . Log . TryAdvancedTraceEvent ( "<prov.SqlConnectionFactory.PruneConnectionPoolGroups|RES|INFO|CPOOL> {0}, ReleasePool={1}" , ObjectId , pool . Id ) ;
853
+ SqlClientEventSource . Metrics . ExitInactiveConnectionPool ( ) ;
854
+ }
855
+ }
856
+ }
857
+ }
858
+ }
859
+
860
+ // Next, walk the pool entry release list and dispose of each pool entry when it is
861
+ // finally empty. If the pool entry isn't empty, it's because there are active pools
862
+ // that need it.
863
+ lock ( _poolGroupsToRelease )
864
+ {
865
+ if ( _poolGroupsToRelease . Count != 0 )
866
+ {
867
+ DbConnectionPoolGroup [ ] poolGroupsToRelease = _poolGroupsToRelease . ToArray ( ) ;
868
+ foreach ( DbConnectionPoolGroup poolGroup in poolGroupsToRelease )
869
+ {
870
+ if ( poolGroup != null )
871
+ {
872
+ int poolsLeft = poolGroup . Clear ( ) ; // may add entries to _poolsToRelease
873
+
874
+ if ( poolsLeft == 0 )
875
+ {
876
+ _poolGroupsToRelease . Remove ( poolGroup ) ;
877
+ SqlClientEventSource . Log . TryAdvancedTraceEvent ( "<prov.SqlConnectionFactory.PruneConnectionPoolGroups|RES|INFO|CPOOL> {0}, ReleasePoolGroup={1}" , ObjectId , poolGroup . ObjectID ) ;
878
+
879
+ SqlClientEventSource . Metrics . ExitInactiveConnectionPoolGroup ( ) ;
880
+ }
881
+ }
882
+ }
883
+ }
884
+ }
885
+
886
+ // Finally, we walk through the collection of connection pool entries and prune each
887
+ // one. This will cause any empty pools to be put into the release list.
888
+ lock ( this )
889
+ {
890
+ Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > connectionPoolGroups = _connectionPoolGroups ;
891
+ Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > newConnectionPoolGroups = new Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > ( connectionPoolGroups . Count ) ;
892
+
893
+ foreach ( KeyValuePair < DbConnectionPoolKey , DbConnectionPoolGroup > entry in connectionPoolGroups )
894
+ {
895
+ if ( entry . Value != null )
896
+ {
897
+ Debug . Assert ( ! entry . Value . IsDisabled , "Disabled pool entry discovered" ) ;
898
+
899
+ // entries start active and go idle during prune if all pools are gone
900
+ // move idle entries from last prune pass to a queue for pending release
901
+ // otherwise process entry which may move it from active to idle
902
+ if ( entry . Value . Prune ( ) )
903
+ {
904
+ // may add entries to _poolsToRelease
905
+ QueuePoolGroupForRelease ( entry . Value ) ;
906
+ }
907
+ else
908
+ {
909
+ newConnectionPoolGroups . Add ( entry . Key , entry . Value ) ;
910
+ }
911
+ }
912
+ }
913
+ _connectionPoolGroups = newConnectionPoolGroups ;
914
+ }
915
+ }
806
916
807
917
private void TryGetConnectionCompletedContinuation ( Task < DbConnectionInternal > task , object state )
808
918
{
0 commit comments