app/BTCPayApp.Core/AsyncDuplicateLock.cs
2024-03-21 18:58:35 +01:00

84 lines
2.6 KiB
C#

using System.Collections.Concurrent;
namespace BTCPayApp.Core;
///from https://stackoverflow.com/a/31194647/275504
public sealed class AsyncDuplicateLock
{
private sealed class RefCounted<T>
{
public RefCounted(T value)
{
RefCount = 1;
Value = value;
}
public int RefCount { get; set; }
public T Value { get; private set; }
}
private readonly ConcurrentDictionary<object, RefCounted<SemaphoreSlim>?> _semaphoreSlims = new();
private SemaphoreSlim GetOrCreate(object key)
{
RefCounted<SemaphoreSlim>? item;
lock (_semaphoreSlims)
{
if (_semaphoreSlims.TryGetValue(key, out item) && item is { })
{
++item.RefCount;
}
else
{
item = new RefCounted<SemaphoreSlim>(new SemaphoreSlim(1, 1));
_semaphoreSlims[key] = item;
}
}
return item.Value;
}
// get a lock for a specific key, and wait until it is available
public async Task<IDisposable> LockAsync(object key, CancellationToken cancellationToken = default)
{
await GetOrCreate(key).WaitAsync(cancellationToken).ConfigureAwait(false);
return new Releaser(_semaphoreSlims, key);
}
// get a lock for a specific key if it is available, or return null if it is currently locked
public async Task<IDisposable?> LockOrBustAsync(object key, CancellationToken cancellationToken = default)
{
var semaphore = GetOrCreate(key);
if (semaphore.CurrentCount == 0)
return null;
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
return new Releaser(_semaphoreSlims, key);
}
private sealed class Releaser : IDisposable
{
private readonly ConcurrentDictionary<object, RefCounted<SemaphoreSlim>?> _semaphoreSlims;
public Releaser(ConcurrentDictionary<object, RefCounted<SemaphoreSlim>?> semaphoreSlims, object key)
{
_semaphoreSlims = semaphoreSlims;
Key = key;
}
private object Key { get; set; }
public void Dispose()
{
RefCounted<SemaphoreSlim>? item;
lock (_semaphoreSlims)
{
if (_semaphoreSlims.TryGetValue(Key, out item) && item is { })
{
--item.RefCount;
if (item.RefCount == 0)
_semaphoreSlims.TryRemove(Key, out _);
}
}
item?.Value.Release();
}
}
}