1- using  Microsoft . Extensions . Hosting ; 
1+ using  System . Runtime . InteropServices ; 
2+ using  Microsoft . Extensions . Hosting ; 
23using  Microsoft . Extensions . Logging ; 
34using  Microsoft . Extensions . Options ; 
45using  ModelContextProtocol . Server ; 
@@ -21,6 +22,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
2122        { 
2223            ArgumentOutOfRangeException . ThrowIfLessThan ( options . Value . IdleTimeout ,  TimeSpan . Zero ) ; 
2324        } 
25+ 
2426        ArgumentOutOfRangeException . ThrowIfLessThan ( options . Value . MaxIdleSessionCount ,  0 ) ; 
2527
2628        try 
@@ -31,8 +33,11 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
3133            var  idleTimeoutTicks  =  options . Value . IdleTimeout . Ticks ; 
3234            var  maxIdleSessionCount  =  options . Value . MaxIdleSessionCount ; 
3335
34-             // The default ValueTuple Comparer will check the first item then the second which preserves both order and uniqueness. 
35-             var  idleSessions  =  new  SortedSet < ( long  Timestamp ,  string  SessionId ) > ( ) ; 
36+             // Create two lists that will be reused between runs. 
37+             // This assumes that the number of idle sessions is not breached frequently. 
38+             // If the idle sessions often breach the maximum, a priority queue could be considered. 
39+             var  idleSessionsTimestamps  =  new  List < long > ( ) ; 
40+             var  idleSessionSessionIds  =  new  List < string > ( ) ; 
3641
3742            while  ( ! stoppingToken . IsCancellationRequested  &&  await  timer . WaitForNextTickAsync ( stoppingToken ) ) 
3843            { 
@@ -56,26 +61,34 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
5661                        continue ; 
5762                    } 
5863
59-                     idleSessions . Add ( ( session . LastActivityTicks ,  session . Id ) ) ; 
64+                     // Add the timestamp and the session 
65+                     idleSessionsTimestamps . Add ( session . LastActivityTicks ) ; 
66+                     idleSessionSessionIds . Add ( session . Id ) ; 
6067
6168                    // Emit critical log at most once every 5 seconds the idle count it exceeded, 
6269                    // since the IdleTimeout will no longer be respected. 
63-                     if  ( idleSessions . Count  ==  maxIdleSessionCount  +  1 ) 
70+                     if  ( idleSessionsTimestamps . Count  ==  maxIdleSessionCount  +  1 ) 
6471                    { 
6572                        LogMaxSessionIdleCountExceeded ( maxIdleSessionCount ) ; 
6673                    } 
6774                } 
6875
69-                 if  ( idleSessions . Count  >  maxIdleSessionCount ) 
76+                 if  ( idleSessionsTimestamps . Count  >  maxIdleSessionCount ) 
7077                { 
71-                     var  sessionsToPrune  =  idleSessions . ToArray ( ) [ ..^ maxIdleSessionCount ] ; 
72-                     foreach  ( var  ( _,  id )  in  sessionsToPrune ) 
78+                     var  timestamps  =  CollectionsMarshal . AsSpan ( idleSessionsTimestamps ) ; 
79+ 
80+                     // Sort only if the maximum is breached and sort solely by the timestamp. Sort both collections. 
81+                     timestamps . Sort ( CollectionsMarshal . AsSpan ( idleSessionSessionIds ) ) ; 
82+ 
83+                     var  sessionsToPrune  =  CollectionsMarshal . AsSpan ( idleSessionSessionIds ) [ ..^ maxIdleSessionCount ] ; 
84+                     foreach  ( var  id  in  sessionsToPrune ) 
7385                    { 
7486                        RemoveAndCloseSession ( id ) ; 
7587                    } 
7688                } 
7789
78-                 idleSessions . Clear ( ) ; 
90+                 idleSessionsTimestamps . Clear ( ) ; 
91+                 idleSessionSessionIds . Clear ( ) ; 
7992            } 
8093        } 
8194        catch  ( OperationCanceledException )  when  ( stoppingToken . IsCancellationRequested ) 
@@ -145,4 +158,4 @@ private async Task DisposeSessionAsync(HttpMcpSession<StreamableHttpServerTransp
145158
146159    [ LoggerMessage ( Level  =  LogLevel . Critical ,  Message  =  "The IdleTrackingBackgroundService has stopped unexpectedly." ) ] 
147160    private  partial  void  IdleTrackingBackgroundServiceStoppedUnexpectedly ( ) ; 
148- } 
161+ } 
0 commit comments