Skip to content

Commit a3a4e47

Browse files
authored
Merge pull request #377 from FoundatioFx/perf/cache-remove-all
Tests for RemoveAllAsync + some tweaks to cache maintenance.
2 parents 4d45ccf + bf77f79 commit a3a4e47

File tree

5 files changed

+133
-12
lines changed

5 files changed

+133
-12
lines changed

src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,64 @@ public virtual async Task CanUseScopedCachesAsync()
357357
}
358358
}
359359

360+
public virtual async Task CanRemoveAllAsync()
361+
{
362+
const int COUNT = 10000;
363+
364+
var cache = GetCacheClient();
365+
if (cache == null)
366+
return;
367+
368+
using (cache)
369+
{
370+
await cache.RemoveAllAsync();
371+
372+
var dictionary = Enumerable.Range(0, COUNT).ToDictionary(i => $"remove-all:{i}");
373+
374+
var sw = Stopwatch.StartNew();
375+
await cache.SetAllAsync(dictionary);
376+
sw.Stop();
377+
_logger.LogInformation("Set All Time: {Elapsed:g}", sw.Elapsed);
378+
379+
sw = Stopwatch.StartNew();
380+
Assert.Equal(COUNT, await cache.RemoveAllAsync());
381+
sw.Stop();
382+
_logger.LogInformation("Remove All Time: {Elapsed:g}", sw.Elapsed);
383+
384+
Assert.False(await cache.ExistsAsync("remove-all:0"));
385+
Assert.False(await cache.ExistsAsync($"remove-all:{COUNT - 1}"));
386+
}
387+
}
388+
389+
public virtual async Task CanRemoveAllKeysAsync()
390+
{
391+
const int COUNT = 10000;
392+
393+
var cache = GetCacheClient();
394+
if (cache == null)
395+
return;
396+
397+
using (cache)
398+
{
399+
await cache.RemoveAllAsync();
400+
401+
var dictionary = Enumerable.Range(0, COUNT).ToDictionary(i => $"remove-all-keys:{i}");
402+
403+
var sw = Stopwatch.StartNew();
404+
await cache.SetAllAsync(dictionary);
405+
sw.Stop();
406+
_logger.LogInformation("Set All Time: {Elapsed:g}", sw.Elapsed);
407+
408+
sw = Stopwatch.StartNew();
409+
Assert.Equal(COUNT, await cache.RemoveAllAsync(dictionary.Keys));
410+
sw.Stop();
411+
_logger.LogInformation("Remove All Time: {Elapsed:g}", sw.Elapsed);
412+
413+
Assert.False(await cache.ExistsAsync("remove-all-keys:0"));
414+
Assert.False(await cache.ExistsAsync($"remove-all-keys:{COUNT - 1}"));
415+
}
416+
}
417+
360418
public virtual async Task CanRemoveByPrefixAsync()
361419
{
362420
var cache = GetCacheClient();
@@ -1036,7 +1094,7 @@ public virtual async Task MeasureThroughputAsync()
10361094
await cache.SetAsync("test", 13422);
10371095
await cache.SetAsync("flag", true);
10381096
Assert.Equal(13422, (await cache.GetAsync<int>("test")).Value);
1039-
Assert.Null(await cache.GetAsync<int>("test2"));
1097+
Assert.False((await cache.GetAsync<int>("test2")).HasValue);
10401098
Assert.True((await cache.GetAsync<bool>("flag")).Value);
10411099
}
10421100
sw.Stop();

src/Foundatio.TestHarness/Caching/HybridCacheClientTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ public override Task CanSetAndGetObjectAsync()
8181
return base.CanSetAndGetObjectAsync();
8282
}
8383

84+
[Fact]
85+
public override Task CanRemoveAllAsync()
86+
{
87+
return base.CanRemoveAllAsync();
88+
}
89+
90+
[Fact]
91+
public override Task CanRemoveAllKeysAsync()
92+
{
93+
return base.CanRemoveAllKeysAsync();
94+
}
95+
8496
[Fact]
8597
public override Task CanRemoveByPrefixAsync()
8698
{

src/Foundatio/Caching/InMemoryCacheClient.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ private async Task<bool> SetInternalAsync(string key, CacheEntry entry, bool add
715715
if (isTraceLogLevelEnabled) _logger.LogTrace("Set cache key: {Key}", key);
716716
}
717717

718-
await StartMaintenanceAsync(true).AnyContext();
718+
await StartMaintenanceAsync(ShouldCompact).AnyContext();
719719
return wasUpdated;
720720
}
721721

@@ -957,18 +957,22 @@ private async Task StartMaintenanceAsync(bool compactImmediately = false)
957957
if (compactImmediately)
958958
await CompactAsync().AnyContext();
959959

960-
if (TimeSpan.FromMilliseconds(100) < utcNow - _lastMaintenance)
960+
if (TimeSpan.FromMilliseconds(250) < utcNow - _lastMaintenance)
961961
{
962962
_lastMaintenance = utcNow;
963963
_ = Task.Run(DoMaintenanceAsync);
964964
}
965965
}
966966

967+
private bool ShouldCompact => _maxItems.HasValue && _memory.Count > _maxItems;
968+
967969
private async Task CompactAsync()
968970
{
969-
if (!_maxItems.HasValue || _memory.Count <= _maxItems)
971+
if (!ShouldCompact)
970972
return;
971973

974+
_logger.LogTrace("CompactAsync: Compacting cache");
975+
972976
string expiredKey = null;
973977
using (await _lock.LockAsync().AnyContext())
974978
{
@@ -1014,7 +1018,11 @@ private async Task DoMaintenanceAsync()
10141018
foreach (var kvp in _memory.ToArray())
10151019
{
10161020
bool lastAccessTimeIsInfrequent = kvp.Value.LastAccessTicks < lastAccessMaximumTicks;
1017-
if (lastAccessTimeIsInfrequent && kvp.Value.ExpiresAt < DateTime.MaxValue && kvp.Value.ExpiresAt <= utcNow)
1021+
if (!lastAccessTimeIsInfrequent)
1022+
continue;
1023+
1024+
var expiresAt = kvp.Value.ExpiresAt;
1025+
if (expiresAt < DateTime.MaxValue && expiresAt <= utcNow)
10181026
{
10191027
_logger.LogDebug("DoMaintenance: Removing expired key {Key}", kvp.Key);
10201028
RemoveKeyIfExpired(kvp.Key);
@@ -1023,10 +1031,11 @@ private async Task DoMaintenanceAsync()
10231031
}
10241032
catch (Exception ex)
10251033
{
1026-
_logger.LogError(ex, "Error trying to find expired cache items");
1034+
_logger.LogError(ex, "Error trying to find expired cache items: {Message}", ex.Message);
10271035
}
10281036

1029-
await CompactAsync().AnyContext();
1037+
if (ShouldCompact)
1038+
await CompactAsync().AnyContext();
10301039
}
10311040

10321041
public void Dispose()

tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ public override Task CanSetAndGetObjectAsync()
7777
return base.CanSetAndGetObjectAsync();
7878
}
7979

80+
[Fact]
81+
public override Task CanRemoveAllAsync()
82+
{
83+
return base.CanRemoveAllAsync();
84+
}
85+
86+
[Fact]
87+
public override Task CanRemoveAllKeysAsync()
88+
{
89+
return base.CanRemoveAllKeysAsync();
90+
}
91+
8092
[Fact]
8193
public override Task CanRemoveByPrefixAsync()
8294
{
@@ -187,10 +199,28 @@ public override Task CanManageListRemoveExpirationAsync()
187199
return base.CanManageListRemoveExpirationAsync();
188200
}
189201

202+
[Fact]
203+
public override Task MeasureThroughputAsync()
204+
{
205+
return base.MeasureThroughputAsync();
206+
}
207+
208+
[Fact]
209+
public override Task MeasureSerializerSimpleThroughputAsync()
210+
{
211+
return base.MeasureSerializerSimpleThroughputAsync();
212+
}
213+
214+
[Fact]
215+
public override Task MeasureSerializerComplexThroughputAsync()
216+
{
217+
return base.MeasureSerializerComplexThroughputAsync();
218+
}
219+
190220
[Fact]
191221
public async Task CanSetMaxItems()
192222
{
193-
// run in tight loop so that the code is warmed up and we can catch timing issues
223+
// run in a tight loop so that the code is warmed up and we can catch timing issues
194224
for (int x = 0; x < 5; x++)
195225
{
196226
var cache = new InMemoryCacheClient(o => o.MaxItems(10).CloneValues(true));

tests/Foundatio.Tests/Caching/InMemoryHybridCacheClientTests.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Threading.Tasks;
1+
using System.Threading.Tasks;
22
using Foundatio.Caching;
33
using Foundatio.Messaging;
44
using Microsoft.Extensions.Logging;
@@ -34,6 +34,18 @@ public override Task CanTryGetAsync()
3434
return base.CanTryGetAsync();
3535
}
3636

37+
[Fact]
38+
public override Task CanRemoveAllAsync()
39+
{
40+
return base.CanRemoveAllAsync();
41+
}
42+
43+
[Fact]
44+
public override Task CanRemoveAllKeysAsync()
45+
{
46+
return base.CanRemoveAllKeysAsync();
47+
}
48+
3749
[Fact]
3850
public override Task CanRemoveByPrefixAsync()
3951
{
@@ -90,19 +102,19 @@ public override Task WillWorkWithSets()
90102
return base.WillWorkWithSets();
91103
}
92104

93-
[Fact(Skip = "Performance Test")]
105+
[Fact]
94106
public override Task MeasureThroughputAsync()
95107
{
96108
return base.MeasureThroughputAsync();
97109
}
98110

99-
[Fact(Skip = "Performance Test")]
111+
[Fact]
100112
public override Task MeasureSerializerSimpleThroughputAsync()
101113
{
102114
return base.MeasureSerializerSimpleThroughputAsync();
103115
}
104116

105-
[Fact(Skip = "Performance Test")]
117+
[Fact]
106118
public override Task MeasureSerializerComplexThroughputAsync()
107119
{
108120
return base.MeasureSerializerComplexThroughputAsync();

0 commit comments

Comments
 (0)