Skip to content

Commit b8b3b6b

Browse files
committed
Throw exception when a nested call to MemoryCache.Get<T>() is made with identical keys to prevent deadlocks.
1 parent 1c72b14 commit b8b3b6b

2 files changed

Lines changed: 22 additions & 3 deletions

File tree

src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ namespace SmartStore.Core.Caching
1515
{
1616
public partial class MemoryCacheManager : DisposableObject, ICacheManager
1717
{
18+
const string LockRecursionExceptionMessage = "Acquiring identical cache items recursively is not supported. Key: {0}";
19+
1820
// Wwe put a special string into cache if value is null,
1921
// otherwise our 'Contains()' would always return false,
2022
// which is bad if we intentionally wanted to save NULL values.
@@ -79,8 +81,14 @@ public T Get<T>(string key, Func<T> acquirer, TimeSpan? duration = null, bool in
7981
return value;
8082
}
8183

84+
var lockKey = "cache:" + key;
85+
if (KeyedLock.IsLockHeld(lockKey))
86+
{
87+
throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key));
88+
}
89+
8290
// Get the (semaphore) locker specific to this key
83-
using (KeyedLock.Lock(key))
91+
using (KeyedLock.Lock(lockKey, TimeSpan.FromMinutes(1)))
8492
{
8593
// Atomic operation must be outer locked
8694
if (!TryGet(key, independent, out value))
@@ -104,8 +112,14 @@ public async Task<T> GetAsync<T>(string key, Func<Task<T>> acquirer, TimeSpan? d
104112
return value;
105113
}
106114

115+
var lockKey = "cache:" + key;
116+
if (KeyedLock.IsLockHeld(lockKey))
117+
{
118+
throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key));
119+
}
120+
107121
// Get the async (semaphore) locker specific to this key
108-
using (await KeyedLock.LockAsync(key))
122+
using (await KeyedLock.LockAsync(lockKey, TimeSpan.FromMinutes(1)))
109123
{
110124
if (!TryGet(key, independent, out value))
111125
{

src/Libraries/SmartStore.Core/Utilities/Threading/KeyedLock.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ private KeyedLock(object key)
2222
private object Key { get; set; }
2323
private SemaphoreSlim Semaphore { get; set; }
2424

25-
public bool IsLockHeld
25+
public bool HasWaiters
2626
{
2727
get { return _waiterCount > 1; }
2828
}
@@ -38,6 +38,11 @@ public int WaiterCount
3838
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3939
public void DecrementCount() => Interlocked.Decrement(ref _waiterCount);
4040

41+
public static bool IsLockHeld(object key)
42+
{
43+
return _locks.ContainsKey(key);
44+
}
45+
4146
public static IDisposable Lock(object key)
4247
{
4348
return Lock(key, _noTimeout);

0 commit comments

Comments
 (0)