Skip to content
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 46 additions & 32 deletions src/Foundatio.Redis/Cache/RedisCacheClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ private CacheValue<T> RedisValueToCacheValue<T>(RedisValue redisValue)
}
}


public async Task<IDictionary<string, CacheValue<T>>> GetAllAsync<T>(IEnumerable<string> keys)
{
if (keys is null)
Expand All @@ -256,6 +257,7 @@ public async Task<IDictionary<string, CacheValue<T>>> GetAllAsync<T>(IEnumerable
if (redisKeys.Length == 0)
return result;

// parallelize?
if (_options.ConnectionMultiplexer.IsCluster())
{
foreach (var hashSlotGroup in redisKeys.GroupBy(k => _options.ConnectionMultiplexer.HashSlot(k)))
Expand Down Expand Up @@ -284,19 +286,25 @@ public async Task<CacheValue<ICollection<T>>> GetListAsync<T>(string key, int? p
if (page is < 1)
throw new ArgumentOutOfRangeException(nameof(page), "Page cannot be less than 1");

await RemoveExpiredListValuesAsync<T>(key, typeof(T) == typeof(string)).AnyContext();

if (!page.HasValue)
try
{
var set = await Database.SortedSetRangeByScoreAsync(key, flags: _options.ReadMode).AnyContext();
return RedisValuesToCacheValue<T>(set);
if (!page.HasValue)
{
var set = await Database.SortedSetRangeByScoreAsync(key, _timeProvider.GetUtcNow().ToUnixTimeMilliseconds() + 1, flags: _options.ReadMode).AnyContext();
Copy link

Copilot AI Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number '+1' added to the timestamp lacks explanation. Consider adding a comment explaining why this offset is necessary (e.g., to include items with scores equal to the current timestamp).

Copilot uses AI. Check for mistakes.
return RedisValuesToCacheValue<T>(set);
}
else
{
long skip = (page.Value - 1) * pageSize;
var set = await Database.SortedSetRangeByScoreAsync(key, _timeProvider.GetUtcNow().ToUnixTimeMilliseconds() + 1, skip: skip, take: pageSize, flags: _options.ReadMode).AnyContext();
Copy link

Copilot AI Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number '+1' added to the timestamp lacks explanation. Consider adding a comment explaining why this offset is necessary for consistency with the non-paginated query.

Copilot uses AI. Check for mistakes.
return RedisValuesToCacheValue<T>(set);
}
}
else
catch (RedisServerException ex) when (ex.Message.StartsWith("WRONGTYPE"))
{
long start = (page.Value - 1) * pageSize;
long end = start + pageSize - 1;
var set = await Database.SortedSetRangeByRankAsync(key, start, end, flags: _options.ReadMode).AnyContext();
return RedisValuesToCacheValue<T>(set);
_logger.LogInformation(ex, "Migrating legacy set to sorted set for key: {Key}", key);
await MigrateLegacySetToSortedSetForKeyAsync<T>(key, typeof(T) == typeof(string)).AnyContext();
return await GetListAsync<T>(key, page, pageSize).AnyContext();
}
}

Expand Down Expand Up @@ -375,7 +383,10 @@ private async Task SetListExpirationAsync(string key)
{
var items = await Database.SortedSetRangeByRankWithScoresAsync(key, 0, 0, order: Order.Descending).AnyContext();
if (items.Length == 0)
{
_logger.LogTrace("Sorted set is empty for key: {Key}, expiration will not be set", key);
return;
}

long highestExpirationInMs = (long)items.Single().Score;
if (highestExpirationInMs > MaxUnixEpochMilliseconds)
Expand Down Expand Up @@ -403,32 +414,35 @@ private async Task RemoveExpiredListValuesAsync<T>(string key, bool isStringValu
catch (RedisServerException ex) when (ex.Message.StartsWith("WRONGTYPE"))
{
_logger.LogInformation(ex, "Migrating legacy set to sorted set for key: {Key}", key);
await MigrateLegacySetToSortedSetForKeyAsync<T>(key, isStringValues).AnyContext();
}
}

// convert legacy set to sorted set
var oldItems = await Database.SetMembersAsync(key).AnyContext();
await Database.KeyDeleteAsync(key).AnyContext();

var currentKeyExpiresIn = await GetExpirationAsync(key).AnyContext();
if (isStringValues)
{
var oldItemValues = new List<string>(oldItems.Length);
foreach (string oldItem in RedisValuesToCacheValue<string>(oldItems).Value)
oldItemValues.Add(oldItem);

await ListAddAsync(key, oldItemValues, currentKeyExpiresIn).AnyContext();
}
else
{
var oldItemValues = new List<T>(oldItems.Length);
foreach (var oldItem in RedisValuesToCacheValue<T>(oldItems).Value)
oldItemValues.Add(oldItem);
private async Task MigrateLegacySetToSortedSetForKeyAsync<T>(string key, bool isStringValues)
{
// convert legacy set to sorted set
var oldItems = await Database.SetMembersAsync(key).AnyContext();
var currentKeyExpiresIn = await GetExpirationAsync(key).AnyContext();
await Database.KeyDeleteAsync(key).AnyContext();
if (isStringValues)
{
var oldItemValues = new List<string>(oldItems.Length);
foreach (string oldItem in RedisValuesToCacheValue<string>(oldItems).Value)
oldItemValues.Add(oldItem);

await ListAddAsync(key, oldItemValues, currentKeyExpiresIn).AnyContext();
}
await ListAddAsync(key, oldItemValues, currentKeyExpiresIn).AnyContext();
}
else
{
var oldItemValues = new List<T>(oldItems.Length);
foreach (var oldItem in RedisValuesToCacheValue<T>(oldItems).Value)
oldItemValues.Add(oldItem);

if (currentKeyExpiresIn.HasValue)
await Database.KeyExpireAsync(key, (DateTime?)null).AnyContext();
await ListAddAsync(key, oldItemValues, currentKeyExpiresIn).AnyContext();
}

if (currentKeyExpiresIn.HasValue)
await Database.KeyExpireAsync(key, (DateTime?)null).AnyContext();
}

public Task<bool> SetAsync<T>(string key, T value, TimeSpan? expiresIn = null)
Expand Down
Loading