Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 8 additions & 95 deletions R5.Internals/R5.Internals.Caching/Caches/AsyncLazyCache.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Caching.Memory;
using AsyncKeyedLock;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
Expand All @@ -19,7 +20,11 @@ public interface IAsyncLazyCache : IDisposable
public class AsyncLazyCache : IAsyncLazyCache
{
private readonly IMemoryCache _cache;
private static readonly KeyedLocks _locks = new KeyedLocks();
private static readonly AsyncKeyedLocker<string> _locks = new AsyncKeyedLocker<string>(o =>
{
o.PoolSize = 20;
o.PoolInitialFill = 1;
});

public AsyncLazyCache(IMemoryCache cache)
{
Expand All @@ -42,7 +47,7 @@ public async Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factoryTask)
return value;
}

using (await _locks.LockAsync(key))
using (await _locks.LockAsync(key).ConfigureAwait(false))
{
if (!_cache.TryGetValue(key, out _))
{
Expand Down Expand Up @@ -102,98 +107,6 @@ public void Dispose()
{
_cache?.Dispose();
}

// internal abstraction to manage the creating/releasing of keyed semaphores
private sealed class KeyedLocks
{
private static readonly Dictionary<string, CountedLock> _locks
= new Dictionary<string, CountedLock>(StringComparer.OrdinalIgnoreCase);

public IDisposable Lock(string key)
{
AcquireLock(key).Wait();
return new LockReleaser(key);
}

public async Task<IDisposable> LockAsync(string key)
{
await AcquireLock(key).WaitAsync().ConfigureAwait(false);
return new LockReleaser(key);
}

// Returns the semaphore associated to the key. Either creates
// a new one and adds to the map, or returns the already existing
// one (additionally incrementing its referenced count)
private SemaphoreSlim AcquireLock(string key)
{
CountedLock exclusiveLock;
lock (_locks)
{
if (_locks.TryGetValue(key, out exclusiveLock))
{
exclusiveLock.Increment();
}
else
{
exclusiveLock = new CountedLock();
_locks[key] = exclusiveLock;
}
}

return exclusiveLock.Lock;
}

// Wrapper around a semaphore - keeps track of the count the key was referenced
private sealed class CountedLock
{
public SemaphoreSlim Lock { get; } = new SemaphoreSlim(1, 1);
private int _count { get; set; } = 1;

public void Increment()
{
_count++;
}

public void Decrement()
{
_count--;
}

public bool NotReferenced()
{
return _count == 0;
}
}

// IDisposable type returned after acquiring a lock. It will handle removing the
// semaphore from the map if releasing results in no more references to the key.
private sealed class LockReleaser : IDisposable
{
private string _key { get; }

public LockReleaser(string key)
{
_key = key;
}

public void Dispose()
{
CountedLock exclusiveLock;
lock (_locks)
{
exclusiveLock = _locks[_key];

exclusiveLock.Decrement();
if (!exclusiveLock.NotReferenced())
{
_locks.Remove(_key);
}
}

exclusiveLock.Lock.Release();
}
}
}
}

public static class ServiceCollectionExtensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AsyncKeyedLock" Version="6.3.4" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.2.0" />
</ItemGroup>
</Project>