Skip to content

Commit a3d7561

Browse files
committed
Redis cache list cleanup
1 parent 5e80c21 commit a3d7561

1 file changed

Lines changed: 17 additions & 25 deletions

File tree

src/Foundatio.Redis/Cache/RedisCacheClient.cs

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ public async Task<CacheValue<ICollection<T>>> GetListAsync<T>(string key, int? p
281281
if (page is < 1)
282282
throw new ArgumentOutOfRangeException(nameof(page), "Page cannot be less than 1");
283283

284-
await ExpireListValuesAsync<T>(key, typeof(T) == typeof(string)).AnyContext();
284+
await RemoveExpiredListValuesAsync<T>(key, typeof(T) == typeof(string)).AnyContext();
285285

286286
if (!page.HasValue)
287287
{
@@ -311,37 +311,28 @@ public async Task<long> ListAddAsync<T>(string key, IEnumerable<T> values, TimeS
311311
throw new ArgumentNullException(nameof(key), "Key cannot be null or empty");
312312

313313
if (values == null)
314-
throw new ArgumentNullException(nameof(values));
314+
return 0;
315315

316316
var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue;
317317
if (expiresAt < _timeProvider.GetUtcNow().UtcDateTime)
318-
{
319-
await ListRemoveAsync(key, values).AnyContext();
320-
return 0;
321-
}
318+
return await ListRemoveAsync(key, values).AnyContext();
322319

323320
var redisValues = new List<SortedSetEntry>();
324321
long expiresAtMilliseconds = expiresAt.ToUnixTimeMilliseconds();
325322

326323
if (values is string stringValue)
327-
{
328324
redisValues.Add(new SortedSetEntry(stringValue.ToRedisValue(_options.Serializer), expiresAtMilliseconds));
329-
}
330325
else
331-
{
332-
var valuesArray = values.Where(v => v is not null).ToArray();
333-
foreach (var value in valuesArray)
334-
redisValues.Add(new SortedSetEntry(value.ToRedisValue(_options.Serializer), expiresAtMilliseconds));
335-
}
326+
redisValues.AddRange(values.Where(v => v is not null).Select(value => new SortedSetEntry(value.ToRedisValue(_options.Serializer), expiresAtMilliseconds)));
336327

337-
await ExpireListValuesAsync<T>(key, values is string).AnyContext();
328+
await RemoveExpiredListValuesAsync<T>(key, values is string).AnyContext();
338329

339330
if (redisValues.Count == 0)
340331
return 0;
341332

342333
long added = await Database.SortedSetAddAsync(key, redisValues.ToArray()).AnyContext();
343334
if (added > 0)
344-
await ExpireListKeyExpirationAsync(key).AnyContext();
335+
await SetListExpirationAsync(key).AnyContext();
345336

346337
return added;
347338
}
@@ -352,46 +343,47 @@ public async Task<long> ListRemoveAsync<T>(string key, IEnumerable<T> values, Ti
352343
throw new ArgumentNullException(nameof(key), "Key cannot be null or empty");
353344

354345
if (values == null)
355-
throw new ArgumentNullException(nameof(values));
346+
return 0;
356347

357348
var redisValues = new List<RedisValue>();
358349
if (values is string stringValue)
359350
redisValues.Add(stringValue.ToRedisValue(_options.Serializer));
360351
else
361-
foreach (var value in values.Where(v => v is not null))
362-
redisValues.Add(value.ToRedisValue(_options.Serializer));
352+
redisValues.AddRange(values.Where(v => v is not null).Select(value => value.ToRedisValue(_options.Serializer)));
363353

364-
await ExpireListValuesAsync<T>(key, values is string).AnyContext();
354+
await RemoveExpiredListValuesAsync<T>(key, values is string).AnyContext();
365355

366356
if (redisValues.Count == 0)
367357
return 0;
368358

369359
long removed = await Database.SortedSetRemoveAsync(key, redisValues.ToArray()).AnyContext();
370360
if (removed > 0)
371-
await ExpireListKeyExpirationAsync(key).AnyContext();
361+
await SetListExpirationAsync(key).AnyContext();
372362

373363
return removed;
374364
}
375365

376-
private async Task ExpireListKeyExpirationAsync(string key)
366+
private const long MaxUnixEpochMilliseconds = 253_402_300_799_999L; // 9999-12-31T23:59:59.999Z
367+
368+
private async Task SetListExpirationAsync(string key)
377369
{
378370
var items = await Database.SortedSetRangeByRankWithScoresAsync(key, 0, 0, order: Order.Descending).AnyContext();
379371
if (items.Length == 0)
380372
return;
381373

382374
long highestExpirationInMs = (long)items.Single().Score;
383-
if (highestExpirationInMs is < -62135596800000 or > 253402300799999)
375+
if (highestExpirationInMs > MaxUnixEpochMilliseconds)
384376
{
385-
_logger.LogWarning("List key {Key} has an invalid expiration value: {Expiration}. Setting Expiration to DateTime.MaxValue", key, highestExpirationInMs);
386-
highestExpirationInMs = 253402300799999;
377+
await SetExpirationAsync(key, TimeSpan.MaxValue).AnyContext();
378+
return;
387379
}
388380

389381
var furthestExpirationUtc = highestExpirationInMs.FromUnixTimeMilliseconds();
390382
var expiresIn = furthestExpirationUtc - _timeProvider.GetUtcNow();
391383
await SetExpirationAsync(key, expiresIn).AnyContext();
392384
}
393385

394-
private async Task ExpireListValuesAsync<T>(string key, bool isStringValues)
386+
private async Task RemoveExpiredListValuesAsync<T>(string key, bool isStringValues)
395387
{
396388
try
397389
{

0 commit comments

Comments
 (0)