Skip to content

Commit e5c3dad

Browse files
fix: per-call MemoryCacheEntryOptions and add TokenLimitExceededException docs
- ResponseIdValidationService: create a fresh MemoryCacheEntryOptions per RecordResponseId call instead of sharing a static instance. The shared instance was safe today (no mutable lists populated) but is a latent hazard if ExpirationTokens or PostEvictionCallbacks are ever added in future — IMemoryCache reads those lists at Set time without defensive copying - TokenLimitExceededException: add XML doc warning that catch-by-base-class (InvalidOperationException) will silently capture this type
1 parent 6400a67 commit e5c3dad

2 files changed

Lines changed: 10 additions & 4 deletions

File tree

EssentialCSharp.Web/Services/ResponseIdValidationService.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ namespace EssentialCSharp.Web.Services;
2020
/// </remarks>
2121
public sealed class ResponseIdValidationService(IMemoryCache cache)
2222
{
23-
private static readonly MemoryCacheEntryOptions _EntryOptions =
24-
new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromHours(2));
25-
2623
/// <summary>
2724
/// Records a newly issued response ID as belonging to the specified user.
2825
/// </summary>
@@ -33,7 +30,11 @@ public void RecordResponseId(string? userId, string? responseId)
3330
return;
3431
}
3532

36-
cache.Set(ResponseKey(responseId), userId, _EntryOptions);
33+
// Create a fresh options instance per call — MemoryCacheEntryOptions has mutable
34+
// list properties (ExpirationTokens, PostEvictionCallbacks) and sharing a static
35+
// instance would cause future additions to affect all entries.
36+
var entryOptions = new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromHours(2));
37+
cache.Set(ResponseKey(responseId), userId, entryOptions);
3738
}
3839

3940
/// <summary>

EssentialCSharp.Web/Services/TokenLimitExceededException.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,10 @@ namespace EssentialCSharp.Web.Services;
55
/// Using a dedicated type (rather than base <see cref="InvalidOperationException"/>) lets callers
66
/// discriminate this expected business-rule failure from unexpected EF Core / runtime failures.
77
/// </summary>
8+
/// <remarks>
9+
/// This type inherits <see cref="InvalidOperationException"/> for convenience. Be aware that any
10+
/// <c>catch (InvalidOperationException)</c> guard added in the call chain will silently capture
11+
/// this exception. Prefer catching <see cref="TokenLimitExceededException"/> directly.
12+
/// </remarks>
813
public sealed class TokenLimitExceededException(int limit)
914
: InvalidOperationException($"You have reached the maximum of {limit} active MCP tokens.");

0 commit comments

Comments
 (0)