Reputation: 20668
I understand items in MemoryCache
are not disposed when expired. I am caching some X509Certificate2
which according to the documentation, should be disposed when done.
However, my naive approach would dispose the object when the object may still be used by some threads (see code below).
How do I correctly handle this case? I think I may need a reference count or something similar?
await cache.GetOrCreateAsync("IdTokenCerts", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = JwtCertsCacheLifetime;
entry.RegisterPostEvictionCallback((_, value, _, _) =>
{
if (value is IEnumerable<SecurityKey> keys)
{
foreach (var key in keys)
{
if (key is X509SecurityKey x509Key)
{
x509Key.Certificate.Dispose();
}
}
}
});
// ...
}
Upvotes: 0
Views: 110
Reputation: 20668
I implemented a simple tracker like this:
public class UsageTracker<TData>(TData data)
{
public event EventHandler OnDisposed = delegate { };
public bool DisposeRequested { get; private set; }
public TData Data => data;
public int UsageCount { get; private set; }
public void RequestDispose()
{
if (DisposeRequested) { return; }
DisposeRequested = true;
CheckForDispose();
}
public UsageTrackerItem<TData> RequestUsage()
{
if (DisposeRequested)
{
throw new ObjectDisposedException(nameof(UsageTracker<TData>));
}
UsageCount++;
return new UsageTrackerItem<TData>(this, data);
}
internal void OnItemDisposed()
{
UsageCount--;
CheckForDispose();
}
void CheckForDispose()
{
if (UsageCount == 0 && DisposeRequested)
{
OnDisposed(this, EventArgs.Empty);
}
}
}
public class UsageTrackerItem<TData> : IDisposable
{
readonly UsageTracker<TData> tracker;
public TData Data { get; private set; }
internal UsageTrackerItem(UsageTracker<TData> tracker, TData data)
{
this.tracker = tracker;
Data = data;
}
public void Dispose()
{
tracker.OnItemDisposed();
}
}
Usage:
var data = 5;
var tracker = new UsageTracker<int>(data);
// Test tracker
tracker.OnDisposed += (sender, e) => Console.WriteLine("Code for disposing items should be here");
var task = Task.WhenAll(Enumerable.Range(0, 10).Select(async i =>
{
using var data = tracker.RequestUsage();
await Task.Delay(Random.Shared.Next(1000, 5000));
Console.WriteLine($"Task {i} completed. {tracker.UsageCount} usages remaining.");
}));
tracker.RequestDispose();
Console.WriteLine("Tracker disposal requested");
try
{
using var staleData = tracker.RequestUsage();
}
catch (Exception ex)
{
Console.WriteLine("There should be this exception due to requesting data after requesting disposal:");
Console.WriteLine(ex.Message);
}
await task;
Output:
Tracker disposal requested
There should be this exception due to requesting data after requesting disposal:
Cannot access a disposed object.
Object name: 'UsageTracker'.
Task 7 completed. 10 usages remaining.
Task 2 completed. 9 usages remaining.
Task 0 completed. 8 usages remaining.
Task 9 completed. 7 usages remaining.
Task 1 completed. 6 usages remaining.
Task 5 completed. 6 usages remaining.
Task 8 completed. 4 usages remaining.
Task 4 completed. 3 usages remaining.
Task 6 completed. 2 usages remaining.
Task 3 completed. 1 usages remaining.
Code for disposing items should be here
Upvotes: 1