Skip to content

Commit 3549367

Browse files
authored
Use a single thread loop to delete expired items (#4)
* Use a single thread loop to delete expired items * Rollback minimum cache loop to 5 minutes * Clean up code * Not exposing class * Using cancellation tokens * Split responsibilities into each class Clean up code
1 parent 5eb6df1 commit 3549367

20 files changed

+476
-460
lines changed

Extensions.Caching.PostgreSql/Columns.cs

+12-14
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,24 @@ namespace Community.Microsoft.Extensions.Caching.PostgreSql
55
{
66
internal static class Columns
77
{
8-
public static class Names
8+
internal static class Names
99
{
10-
public const string CacheItemId = "DistCacheId";
11-
public const string CacheItemValue = "DistCacheValue";
12-
public const string ExpiresAtTime = "DistCacheExpiresAtTime";
13-
public const string SlidingExpirationInSeconds = "DistCacheSlidingExpirationInSeconds";
14-
public const string AbsoluteExpiration = "DistCacheAbsoluteExpiration";
10+
internal const string CacheItemId = "DistCacheId";
11+
internal const string CacheItemValue = "DistCacheValue";
12+
internal const string ExpiresAtTime = "DistCacheExpiresAtTime";
13+
internal const string SlidingExpirationInSeconds = "DistCacheSlidingExpirationInSeconds";
14+
internal const string AbsoluteExpiration = "DistCacheAbsoluteExpiration";
1515
}
1616

17-
public static class Indexes
17+
internal static class Indexes
1818
{
1919
// The value of the following index positions is dependent on how the SQL queries
2020
// are selecting the columns.
21-
public const int CacheItemIdIndex = 0;
22-
public const int CacheItemValueIndex = 1;
23-
public const int ExpiresAtTimeIndex = 2;
24-
public const int SlidingExpirationInSecondsIndex = 3;
25-
public const int AbsoluteExpirationIndex = 4;
26-
27-
21+
internal const int CacheItemIdIndex = 0;
22+
internal const int CacheItemValueIndex = 1;
23+
internal const int ExpiresAtTimeIndex = 2;
24+
internal const int SlidingExpirationInSecondsIndex = 3;
25+
internal const int AbsoluteExpirationIndex = 4;
2826
}
2927
}
3028
}

Extensions.Caching.PostgreSql/Community.Microsoft.Extensions.Caching.PostgreSql.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
</ItemGroup>-->
2929
<ItemGroup>
3030
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
31+
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
3132
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
3233
<PackageReference Include="Npgsql" Version="5.0.0" />
3334
</ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Microsoft.Extensions.Hosting;
5+
using Microsoft.Extensions.Internal;
6+
using Microsoft.Extensions.Options;
7+
8+
namespace Community.Microsoft.Extensions.Caching.PostgreSql
9+
{
10+
internal sealed class DatabaseExpiredItemsRemoverLoop : IDatabaseExpiredItemsRemoverLoop
11+
{
12+
private static readonly TimeSpan MinimumExpiredItemsDeletionInterval = TimeSpan.FromMinutes(5);
13+
private static readonly TimeSpan DefaultExpiredItemsDeletionInterval = TimeSpan.FromMinutes(30);
14+
private readonly TimeSpan _expiredItemsDeletionInterval;
15+
private DateTimeOffset _lastExpirationScan;
16+
private readonly IDatabaseOperations _databaseOperations;
17+
private readonly CancellationTokenSource _cancellationTokenSource;
18+
private readonly ISystemClock _systemClock;
19+
20+
public DatabaseExpiredItemsRemoverLoop(
21+
IOptions<PostgreSqlCacheOptions> options,
22+
IDatabaseOperations databaseOperations,
23+
IHostApplicationLifetime applicationLifetime)
24+
{
25+
var cacheOptions = options.Value;
26+
27+
if (cacheOptions.ExpiredItemsDeletionInterval.HasValue &&
28+
cacheOptions.ExpiredItemsDeletionInterval.Value < MinimumExpiredItemsDeletionInterval)
29+
{
30+
throw new ArgumentException(
31+
$"{nameof(PostgreSqlCacheOptions.ExpiredItemsDeletionInterval)} cannot be less the minimum " +
32+
$"value of {MinimumExpiredItemsDeletionInterval.TotalMinutes} minutes.");
33+
}
34+
35+
_systemClock = cacheOptions.SystemClock;
36+
_cancellationTokenSource = new CancellationTokenSource();
37+
applicationLifetime.ApplicationStopping.Register(OnShutdown);
38+
_databaseOperations = databaseOperations;
39+
_expiredItemsDeletionInterval = cacheOptions.ExpiredItemsDeletionInterval ?? DefaultExpiredItemsDeletionInterval;
40+
}
41+
42+
public void Start()
43+
{
44+
Task.Run(DeleteExpiredCacheItems);
45+
}
46+
47+
private void OnShutdown()
48+
{
49+
_cancellationTokenSource.Cancel();
50+
}
51+
52+
private async Task DeleteExpiredCacheItems()
53+
{
54+
while (true)
55+
{
56+
var utcNow = _systemClock.UtcNow;
57+
if ((utcNow - _lastExpirationScan) > _expiredItemsDeletionInterval)
58+
{
59+
try
60+
{
61+
await _databaseOperations.DeleteExpiredCacheItemsAsync(_cancellationTokenSource.Token);
62+
_lastExpirationScan = utcNow;
63+
}
64+
catch (TaskCanceledException)
65+
{
66+
break;
67+
}
68+
catch (Exception)
69+
{
70+
//We don't want transient errors from failing next run
71+
}
72+
}
73+
74+
try
75+
{
76+
await Task
77+
.Delay(_expiredItemsDeletionInterval, _cancellationTokenSource.Token)
78+
.ConfigureAwait(true);
79+
}
80+
catch (TaskCanceledException)
81+
{
82+
// ignore: Task.Delay throws this exception when ct.IsCancellationRequested = true
83+
// In this case, we only want to stop polling and finish this async Task.
84+
break;
85+
}
86+
}
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)