From 23a9d39bfde8363bc7851e37b6744a5b042fc248 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Sun, 20 Sep 2026 14:12:09 +0200 Subject: [PATCH] AsyncKeyed locker optimizations --- .../LockerComparisonBenchmark.cs | 34 +++--- .../AsyncKeyedMemoryLocker.cs | 106 ++++++++++-------- .../StripedAsyncKeyedMemoryLocker.cs | 101 +++++++++-------- 3 files changed, 131 insertions(+), 110 deletions(-) diff --git a/benchmarks/ZiggyCreatures.FusionCache.Benchmarks/LockerComparisonBenchmark.cs b/benchmarks/ZiggyCreatures.FusionCache.Benchmarks/LockerComparisonBenchmark.cs index f877cef7..aa5f668a 100644 --- a/benchmarks/ZiggyCreatures.FusionCache.Benchmarks/LockerComparisonBenchmark.cs +++ b/benchmarks/ZiggyCreatures.FusionCache.Benchmarks/LockerComparisonBenchmark.cs @@ -25,14 +25,16 @@ public Config() } - [Params(200, 1_000)] - public int NumberOfLocks; + [ParamsSource(nameof(Configurations))] + public (int NumberOfLocks, int Contention) Setting { get; set; } + public (int NumberOfLocks, int Contention)[] Configurations { get; } = + [ + (200, 100), + (200, 10_000), + (10_000, 100) + ]; - [Params(100, 1_000)] - public int Contention; - - [Params(0, 10)] - public int GuidReversals; + [Params(0, 1, 5)] public int GuidReversals { get; set; } private StandardMemoryLocker _StandardMemoryLocker = null!; private ParallelQuery _StandardMemoryLockerTasks = null!; @@ -65,13 +67,13 @@ public void Setup() [IterationSetup] public void IterationSetup() { - List _shuffledIntegerList = [.. Enumerable.Range(0, Contention * NumberOfLocks)]; + List _shuffledIntegerList = [.. Enumerable.Range(0, Setting.Contention * Setting.NumberOfLocks)]; Shuffle(_shuffledIntegerList); _StandardMemoryLockerTasks = _shuffledIntegerList .Select(async i => { - var key = (i % NumberOfLocks).ToString(); + var key = (i % Setting.NumberOfLocks).ToString(); var mylock = await _StandardMemoryLocker.AcquireLockAsync(null!, null!, null!, key, TimeSpan.FromSeconds(5), null, default).ConfigureAwait(false); Operation(); @@ -81,7 +83,7 @@ public void IterationSetup() _ProbabilisticMemoryLockerTasks = _shuffledIntegerList .Select(async i => { - var key = (i % NumberOfLocks).ToString(); + var key = (i % Setting.NumberOfLocks).ToString(); var mylock = await _ProbabilisticMemoryLocker.AcquireLockAsync(null!, null!, null!, key, TimeSpan.FromSeconds(5), null, default).ConfigureAwait(false); Operation(); @@ -91,7 +93,7 @@ public void IterationSetup() _ExperimentalMemoryLockerTasks = _shuffledIntegerList .Select(async i => { - var key = (i % NumberOfLocks).ToString(); + var key = (i % Setting.NumberOfLocks).ToString(); var mylock = await _ExperimentalMemoryLocker.AcquireLockAsync(null!, null!, null!, key, TimeSpan.FromSeconds(5), null, default).ConfigureAwait(false); Operation(); @@ -101,7 +103,7 @@ public void IterationSetup() _AsyncKeyedMemoryLockerTasks = _shuffledIntegerList .Select(async i => { - var key = (i % NumberOfLocks).ToString(); + var key = (i % Setting.NumberOfLocks).ToString(); var mylock = await _AsyncKeyedMemoryLocker.AcquireLockAsync(null!, null!, null!, key, TimeSpan.FromSeconds(5), null, default).ConfigureAwait(false); Operation(); @@ -111,7 +113,7 @@ public void IterationSetup() _StripedAsyncKeyedMemoryLockerTasks = _shuffledIntegerList .Select(async i => { - var key = (i % NumberOfLocks).ToString(); + var key = (i % Setting.NumberOfLocks).ToString(); var mylock = await _StripedAsyncKeyedMemoryLocker.AcquireLockAsync(null!, null!, null!, key, TimeSpan.FromSeconds(5), null, default).ConfigureAwait(false); Operation(); @@ -121,10 +123,6 @@ public void IterationSetup() private async Task RunTests(ParallelQuery tasks) { - if (NumberOfLocks == Contention) - { - throw new Exception("Thrown on purpose"); - } await Task.WhenAll(tasks).ConfigureAwait(false); } @@ -202,7 +200,7 @@ public async Task TestLockAsyncKeyedLock() await RunTests(_AsyncKeyedMemoryLockerTasks).ConfigureAwait(false); } - //[Benchmark] + [Benchmark] public async Task TestLockStripedAsyncKeyedLock() { await RunTests(_StripedAsyncKeyedMemoryLockerTasks).ConfigureAwait(false); diff --git a/src/ZiggyCreatures.FusionCache.Locking.AsyncKeyed/AsyncKeyedMemoryLocker.cs b/src/ZiggyCreatures.FusionCache.Locking.AsyncKeyed/AsyncKeyedMemoryLocker.cs index 9b264d75..ed0f309b 100644 --- a/src/ZiggyCreatures.FusionCache.Locking.AsyncKeyed/AsyncKeyedMemoryLocker.cs +++ b/src/ZiggyCreatures.FusionCache.Locking.AsyncKeyed/AsyncKeyedMemoryLocker.cs @@ -1,5 +1,6 @@ using AsyncKeyedLock; using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; namespace ZiggyCreatures.Caching.Fusion.Locking.AsyncKeyed; @@ -7,62 +8,73 @@ namespace ZiggyCreatures.Caching.Fusion.Locking.AsyncKeyed; /// An implementation of based on AsyncKeyedLocker. /// public sealed class AsyncKeyedMemoryLocker - : IFusionCacheMemoryLocker + : IFusionCacheMemoryLocker { - private readonly AsyncKeyedLocker _locker; + private readonly AsyncKeyedLocker _locker; - /// - /// Initializes a new instance of the class. - /// - public AsyncKeyedMemoryLocker(AsyncKeyedLockOptions? options = null) - { - options ??= new AsyncKeyedLockOptions(); + /// + /// Initializes a new instance of the class. + /// + public AsyncKeyedMemoryLocker(AsyncKeyedLockOptions? options = null) + { + options ??= new AsyncKeyedLockOptions(); - _locker = new AsyncKeyedLocker(options); - } + _locker = new AsyncKeyedLocker(options); + } - /// - public async ValueTask AcquireLockAsync(string cacheName, string cacheInstanceId, string operationId, string key, TimeSpan timeout, ILogger? logger, CancellationToken token) - { - return await _locker.LockOrNullAsync(key, timeout, token).ConfigureAwait(false); - } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask AcquireLockAsync(string cacheName, string cacheInstanceId, string operationId, string key, TimeSpan timeout, ILogger? logger, CancellationToken token) + { + var acquireTask = _locker.LockOrNullAsync(key, timeout, token); - /// - public object? AcquireLock(string cacheName, string cacheInstanceId, string operationId, string key, TimeSpan timeout, ILogger? logger, CancellationToken token) - { - return _locker.LockOrNull(key, timeout, token); - } + return acquireTask.IsCompletedSuccessfully ? + new ValueTask(acquireTask.Result) : + AwaitAcquireLockAsync(acquireTask); + } - /// - public void ReleaseLock(string cacheName, string cacheInstanceId, string operationId, string key, object? lockObj, ILogger? logger) - { - if (lockObj is null) - return; + private static async ValueTask AwaitAcquireLockAsync(ValueTask acquireTask) + { + return await acquireTask.ConfigureAwait(false); + } - try - { - ((IDisposable)lockObj).Dispose(); - } - catch (Exception exc) - { - if (logger?.IsEnabled(LogLevel.Warning) ?? false) - logger.Log(LogLevel.Warning, exc, "FUSION [N={CacheName} I={CacheInstanceId}] (O={CacheOperationId} K={CacheKey}): an error occurred while trying to release an AsyncKeyedLock result in the memory locker", cacheName, cacheInstanceId, operationId, key); - } - } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object? AcquireLock(string cacheName, string cacheInstanceId, string operationId, string key, TimeSpan timeout, ILogger? logger, CancellationToken token) + { + return _locker.LockOrNull(key, timeout, token); + } - // IDISPOSABLE - private bool disposedValue; + /// + public void ReleaseLock(string cacheName, string cacheInstanceId, string operationId, string key, object? lockObj, ILogger? logger) + { + if (lockObj is null) + return; - /// - public void Dispose() - { - if (disposedValue) - { - return; - } + try + { + ((IDisposable)lockObj).Dispose(); + } + catch (Exception exc) + { + if (logger?.IsEnabled(LogLevel.Warning) ?? false) + logger.Log(LogLevel.Warning, exc, "FUSION [N={CacheName} I={CacheInstanceId}] (O={CacheOperationId} K={CacheKey}): an error occurred while trying to release an AsyncKeyedLock result in the memory locker", cacheName, cacheInstanceId, operationId, key); + } + } - _locker?.Dispose(); + // IDISPOSABLE + private bool disposedValue; - disposedValue = true; - } + /// + public void Dispose() + { + if (disposedValue) + { + return; + } + + _locker?.Dispose(); + + disposedValue = true; + } } diff --git a/src/ZiggyCreatures.FusionCache.Locking.AsyncKeyed/StripedAsyncKeyedMemoryLocker.cs b/src/ZiggyCreatures.FusionCache.Locking.AsyncKeyed/StripedAsyncKeyedMemoryLocker.cs index 4d20d2bc..678bfb5d 100644 --- a/src/ZiggyCreatures.FusionCache.Locking.AsyncKeyed/StripedAsyncKeyedMemoryLocker.cs +++ b/src/ZiggyCreatures.FusionCache.Locking.AsyncKeyed/StripedAsyncKeyedMemoryLocker.cs @@ -1,5 +1,6 @@ using AsyncKeyedLock; using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; namespace ZiggyCreatures.Caching.Fusion.Locking.AsyncKeyed; @@ -7,50 +8,60 @@ namespace ZiggyCreatures.Caching.Fusion.Locking.AsyncKeyed; /// An implementation of based on StripedAsyncKeyedLocker. /// public sealed class StripedAsyncKeyedMemoryLocker - : IFusionCacheMemoryLocker + : IFusionCacheMemoryLocker { - private readonly StripedAsyncKeyedLocker _locker; - - /// - /// Initializes a new instance of the class. - /// - public StripedAsyncKeyedMemoryLocker(int numberOfStripes = 4049, int maxCount = 1, IEqualityComparer? comparer = null) - { - _locker = new StripedAsyncKeyedLocker(numberOfStripes, maxCount, comparer); - } - - /// - public async ValueTask AcquireLockAsync(string cacheName, string cacheInstanceId, string operationId, string key, TimeSpan timeout, ILogger? logger, CancellationToken token) - { - return await _locker.LockOrNullAsync(key, timeout, token).ConfigureAwait(false); - } - - /// - public object? AcquireLock(string cacheName, string cacheInstanceId, string operationId, string key, TimeSpan timeout, ILogger? logger, CancellationToken token) - { - return _locker.LockOrNull(key, timeout, token); - } - - /// - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ReleaseLock(string cacheName, string cacheInstanceId, string operationId, string key, object? lockObj, ILogger? logger) - { - if (lockObj is null) - return; - - try - { - ((IDisposable)lockObj).Dispose(); - } - catch (Exception exc) - { - if (logger?.IsEnabled(LogLevel.Warning) ?? false) - logger.Log(LogLevel.Warning, exc, "FUSION [N={CacheName} I={CacheInstanceId}] (O={CacheOperationId} K={CacheKey}): an error occurred while trying to release an AsyncKeyedLock result in the memory locker", cacheName, cacheInstanceId, operationId, key); - } - } - - /// - public void Dispose() - { - } + private readonly StripedAsyncKeyedLocker _locker; + + /// + /// Initializes a new instance of the class. + /// + public StripedAsyncKeyedMemoryLocker(int numberOfStripes = 4049, int maxCount = 1, IEqualityComparer? comparer = null) + { + _locker = new StripedAsyncKeyedLocker(numberOfStripes, maxCount, comparer); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask AcquireLockAsync(string cacheName, string cacheInstanceId, string operationId, string key, TimeSpan timeout, ILogger? logger, CancellationToken token) + { + var acquireTask = _locker.LockOrNullAsync(key, timeout, token); + + return acquireTask.IsCompletedSuccessfully ? + new ValueTask(acquireTask.Result) : + AwaitAcquireLockAsync(acquireTask); + } + + private static async ValueTask AwaitAcquireLockAsync(ValueTask acquireTask) + { + return await acquireTask.ConfigureAwait(false); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object? AcquireLock(string cacheName, string cacheInstanceId, string operationId, string key, TimeSpan timeout, ILogger? logger, CancellationToken token) + { + return _locker.LockOrNull(key, timeout, token); + } + + /// + public void ReleaseLock(string cacheName, string cacheInstanceId, string operationId, string key, object? lockObj, ILogger? logger) + { + if (lockObj is null) + return; + + try + { + ((IDisposable)lockObj).Dispose(); + } + catch (Exception exc) + { + if (logger?.IsEnabled(LogLevel.Warning) ?? false) + logger.Log(LogLevel.Warning, exc, "FUSION [N={CacheName} I={CacheInstanceId}] (O={CacheOperationId} K={CacheKey}): an error occurred while trying to release an AsyncKeyedLock result in the memory locker", cacheName, cacheInstanceId, operationId, key); + } + } + + /// + public void Dispose() + { + } }