-
Notifications
You must be signed in to change notification settings - Fork 689
Expand file tree
/
Copy pathMcpClientTasksLruCache.cs
More file actions
88 lines (77 loc) · 2.78 KB
/
McpClientTasksLruCache.cs
File metadata and controls
88 lines (77 loc) · 2.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace ModelContextProtocol.Client;
/// <summary>
/// A thread-safe Least Recently Used (LRU) cache for MCP client and tools.
/// </summary>
internal sealed class McpClientTasksLruCache : IDisposable
{
private readonly Dictionary<string, (LinkedListNode<string> Node, Task<(McpClient Client, IList<McpClientTool> Tools)> Task)> _cache;
private readonly LinkedList<string> _lruList;
private readonly object _lock = new();
private readonly int _capacity;
public McpClientTasksLruCache(int capacity)
{
Debug.Assert(capacity > 0);
_capacity = capacity;
_cache = new Dictionary<string, (LinkedListNode<string>, Task<(McpClient, IList<McpClientTool>)>)>(capacity);
_lruList = [];
}
public Task<(McpClient Client, IList<McpClientTool> Tools)> GetOrAdd<TState>(string key, Func<string, TState, Task<(McpClient, IList<McpClientTool>)>> valueFactory, TState state)
{
lock (_lock)
{
if (_cache.TryGetValue(key, out var existing))
{
_lruList.Remove(existing.Node);
_lruList.AddLast(existing.Node);
return existing.Task;
}
var value = valueFactory(key, state);
var newNode = _lruList.AddLast(key);
_cache[key] = (newNode, value);
// Evict oldest if over capacity
if (_cache.Count > _capacity)
{
string oldestKey = _lruList.First!.Value;
_lruList.RemoveFirst();
(_, Task<(McpClient Client, IList<McpClientTool> Tools)> task) = _cache[oldestKey];
_cache.Remove(oldestKey);
// Dispose evicted MCP client
if (task.Status == TaskStatus.RanToCompletion)
{
_ = task.Result.Client.DisposeAsync().AsTask();
}
}
return value;
}
}
public bool TryRemove(string key, [MaybeNullWhen(false)] out Task<(McpClient Client, IList<McpClientTool> Tools)>? task)
{
lock (_lock)
{
if (_cache.TryGetValue(key, out var entry))
{
_cache.Remove(key);
_lruList.Remove(entry.Node);
task = entry.Task;
return true;
}
task = null;
return false;
}
}
public void Dispose()
{
lock (_lock)
{
foreach ((_, Task<(McpClient Client, IList<McpClientTool> Tools)> task) in _cache.Values)
{
if (task.Status == TaskStatus.RanToCompletion)
{
_ = task.Result.Client.DisposeAsync().AsTask();
}
}
}
}
}