Skip to content

Commit 929ae58

Browse files
authored
IdleTracking uses lists instead of SortedSet (#629)
1 parent bd5aef6 commit 929ae58

File tree

1 file changed

+23
-10
lines changed

1 file changed

+23
-10
lines changed

src/ModelContextProtocol.AspNetCore/IdleTrackingBackgroundService.cs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.Extensions.Hosting;
1+
using System.Runtime.InteropServices;
2+
using Microsoft.Extensions.Hosting;
23
using Microsoft.Extensions.Logging;
34
using Microsoft.Extensions.Options;
45
using 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

Comments
 (0)