Skip to content
Closed
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
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<ItemGroup>
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.0" />
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageVersion Include="AsyncKeyedLock" Version="7.1.7" />
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
<PackageVersion Include="Examine" Version="3.7.1" />
<PackageVersion Include="Examine.Core" Version="3.7.1" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using AsyncKeyedLock;
using Microsoft.Extensions.Caching.Hybrid;

namespace Umbraco.Cms.Infrastructure.HybridCache.Extensions;
Expand All @@ -10,7 +11,7 @@ internal static class HybridCacheExtensions
{
// Per-key semaphores to ensure the GetOrCreateAsync + RemoveAsync sequence
// executes atomically for a given cache key.
private static readonly ConcurrentDictionary<string, SemaphoreSlim> _keyLocks = new();
private static readonly AsyncKeyedLocker<string> _keyLocks = new();

/// <summary>
/// Returns true if the cache contains an item with a matching key.
Expand Down Expand Up @@ -45,13 +46,7 @@ public static async Task<bool> ExistsAsync<T>(this Microsoft.Extensions.Caching.
{
var exists = true;

// Acquire a per-key semaphore so that GetOrCreateAsync and the possible RemoveAsync
// complete without another thread retrieving/creating the same key in-between.
SemaphoreSlim sem = _keyLocks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));

await sem.WaitAsync().ConfigureAwait(false);

try
using (await _keyLocks.LockAsync(key, token))
{
T? result = await cache.GetOrCreateAsync<T?>(
key,
Expand All @@ -74,16 +69,5 @@ public static async Task<bool> ExistsAsync<T>(this Microsoft.Extensions.Caching.

return (exists, result);
}
finally
{
sem.Release();

// Only remove the semaphore mapping if it still points to the same instance we used.
// This avoids removing another thread's semaphore or corrupting the map.
if (_keyLocks.TryGetValue(key, out SemaphoreSlim? current) && ReferenceEquals(current, sem))
{
_keyLocks.TryRemove(key, out _);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AsyncKeyedLock" />
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" />
<PackageReference Include="MessagePack" />
<PackageReference Include="K4os.Compression.LZ4" />
Expand Down
Loading