Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit fa45aac

Browse files
committed
Concurrency problems
1 parent e88f669 commit fa45aac

File tree

9 files changed

+124
-821
lines changed

9 files changed

+124
-821
lines changed

src/Microsoft.Extensions.Caching.Memory/MemoryCache.cs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace Microsoft.Extensions.Caching.Memory
1919
public class MemoryCache : IMemoryCache
2020
{
2121
private readonly ConcurrentDictionary<object, CacheEntry> _entries;
22+
private readonly SemaphoreSlim _compactionSemaphore = new SemaphoreSlim(initialCount: 1, maxCount: 1);
2223
private bool _disposed;
2324

2425
// We store the delegates locally to prevent allocations
@@ -27,7 +28,7 @@ public class MemoryCache : IMemoryCache
2728
private readonly Action<CacheEntry> _entryExpirationNotification;
2829

2930
private readonly ISystemClock _clock;
30-
private readonly int? _maximumEntriesCount;
31+
private readonly int? _entryCountLimit;
3132

3233
private TimeSpan _expirationScanFrequency;
3334
private DateTimeOffset _lastExpirationScan;
@@ -50,7 +51,7 @@ public MemoryCache(IOptions<MemoryCacheOptions> optionsAccessor)
5051

5152
_clock = options.Clock ?? new SystemClock();
5253
_expirationScanFrequency = options.ExpirationScanFrequency;
53-
_maximumEntriesCount = options.EntryCountLimit;
54+
_entryCountLimit = options.EntryCountLimit;
5455
_lastExpirationScan = _clock.UtcNow;
5556
}
5657

@@ -151,10 +152,10 @@ private void SetEntry(CacheEntry entry)
151152
{
152153
entry.AttachTokens();
153154

154-
// Compact by 10 percent if we exceed the given maximum number of cache entries
155-
if (_entries.Count > _maximumEntriesCount)
155+
// Compact if the given maximum number of cache entries is exceeded
156+
if (_entries.Count > _entryCountLimit)
156157
{
157-
Compact(0.10);
158+
TriggerOvercapacityCompaction();
158159
}
159160
}
160161
else
@@ -280,14 +281,36 @@ private static void ScanForExpiredItems(MemoryCache cache)
280281
}
281282
}
282283

284+
private void TriggerOvercapacityCompaction()
285+
{
286+
if (!_compactionSemaphore.Wait(0))
287+
{
288+
// Another compaction is running, exit immediately.
289+
// Avoid overpurging when multiple overcapacity compactions are triggered concurrently.
290+
return;
291+
}
292+
293+
try
294+
{
295+
// Keep compacting until remaining entries are at 90% of maximum
296+
// Stop compacting if no entries were removed, for example if all the remaining entries are pinned with NeverRemove priority
297+
while (Compact(1 - ((0.9 * _entryCountLimit.Value) / _entries.Count)) && _entries.Count > _entryCountLimit.Value) { }
298+
}
299+
finally
300+
{
301+
_compactionSemaphore.Release();
302+
}
303+
}
304+
283305
/// Remove at least the given percentage (0.10 for 10%) of the total entries (or estimated memory?), according to the following policy:
284306
/// 1. Remove all expired items.
285307
/// 2. Bucket by CacheItemPriority.
286308
/// 3. Least recently used objects.
287309
/// ?. Items with the soonest absolute expiration.
288310
/// ?. Items with the soonest sliding expiration.
289311
/// ?. Larger objects - estimated by object graph size, inaccurate.
290-
public void Compact(double percentage)
312+
/// Returns true if at least one entry was removed, otherwise false.
313+
public bool Compact(double percentage)
291314
{
292315
var entriesToRemove = new List<CacheEntry>();
293316
var lowPriEntries = new List<CacheEntry>();
@@ -329,10 +352,14 @@ public void Compact(double percentage)
329352
ExpirePriorityBucket(removalCountTarget, entriesToRemove, normalPriEntries);
330353
ExpirePriorityBucket(removalCountTarget, entriesToRemove, highPriEntries);
331354

355+
var removedEntries = entriesToRemove.Count > 0;
356+
332357
foreach (var entry in entriesToRemove)
333358
{
334359
RemoveEntry(entry);
335360
}
361+
362+
return removedEntries;
336363
}
337364

338365
/// Policy:

src/Microsoft.Extensions.Caching.Memory/MemoryCacheServiceCollectionExtensions.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,42 @@ public static IServiceCollection AddDistributedMemoryCache(this IServiceCollecti
8080
throw new ArgumentNullException(nameof(services));
8181
}
8282

83-
services.TryAddSingleton<IDistributedCache>(new MemoryDistributedCache(new MemoryCache(new MemoryCacheOptions())));
83+
return services.AddDistributedMemoryCache(_ => { });
84+
}
85+
86+
/// <summary>
87+
/// Adds a default implementation of <see cref="IDistributedCache"/> that stores items in memory
88+
/// to the <see cref="IServiceCollection" />. Frameworks that require a distributed cache to work
89+
/// can safely add this dependency as part of their dependency list to ensure that there is at least
90+
/// one implementation available.
91+
/// </summary>
92+
/// <remarks>
93+
/// <see cref="AddDistributedMemoryCache(IServiceCollection)"/> should only be used in single
94+
/// server scenarios as this cache stores items in memory and doesn't expand across multiple machines.
95+
/// For those scenarios it is recommended to use a proper distributed cache that can expand across
96+
/// multiple machines.
97+
/// </remarks>
98+
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
99+
/// <param name="setupAction">
100+
/// The <see cref="Action{MemoryCacheOptions}"/> to configure the <see cref="MemoryCacheOptions"/> that is used by the <see cref="MemoryDistributedCache"/>.
101+
/// </param>
102+
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
103+
public static IServiceCollection AddDistributedMemoryCache(this IServiceCollection services, Action<MemoryCacheOptions> setupAction)
104+
{
105+
if (services == null)
106+
{
107+
throw new ArgumentNullException(nameof(services));
108+
}
109+
110+
if (setupAction == null)
111+
{
112+
throw new ArgumentNullException(nameof(services));
113+
}
114+
115+
var memoryCacheOptions = new MemoryCacheOptions();
116+
setupAction(memoryCacheOptions);
117+
118+
services.TryAddSingleton<IDistributedCache>(new MemoryDistributedCache(new MemoryCache(memoryCacheOptions)));
84119

85120
return services;
86121
}

0 commit comments

Comments
 (0)