Skip to content

Commit 6237aea

Browse files
authored
Merge pull request #652 from imperugo/fix/issues-650-651
Fix SearchKeysAsync database selection and RemoveByTagAsync tag cleanup
2 parents c8803c3 + 7d570fd commit 6237aea

5 files changed

Lines changed: 61 additions & 18 deletions

File tree

Directory.Build.props

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<!-- General information -->
44
<PropertyGroup>
55
<Authors>Ugo Lattanzi</Authors>
6-
<VersionPrefix>12.1.0</VersionPrefix>
6+
<VersionPrefix>12.2.0</VersionPrefix>
77
<!--
88
<VersionSuffix>pre</VersionSuffix>
99
-->
@@ -46,6 +46,10 @@ Features:
4646
Serializer packages (pick one): System.Text.Json, Newtonsoft, MemoryPack, MsgPack, Protobuf, ServiceStack, Utf8Json.
4747
</Description>
4848
<PackageReleaseNotes>
49+
v12.2.0:
50+
- Fixed SearchKeysAsync ignoring database number — SCAN now correctly targets the specified database instead of always scanning DB0 (#651)
51+
- Fixed RemoveByTagAsync not deleting the tag Set key itself, causing a slow memory leak of empty tag Sets in Redis (#650)
52+
4953
v12.1.0:
5054
- Added VectorSet API for AI/ML similarity search (Redis 8.0+): VADD, VSIM, VREM, VCONTAINS, VCARD, VDIM, VGETATTR, VSETATTR, VINFO, VRANDMEMBER, VLINKS
5155
- Added llms.txt for AI coding assistant documentation indexing

src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.Tags.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ public async Task<long> RemoveByTagAsync(string tag, CommandFlags flag = Command
3838

3939
var keys = await SetMembersAsync<string>(tagKey, flag).ConfigureAwait(false);
4040

41-
return await RemoveAllAsync(keys, flag).ConfigureAwait(false);
41+
var deletedCount = await RemoveAllAsync(keys, flag).ConfigureAwait(false);
42+
43+
await Database.KeyDeleteAsync(tagKey, flag).ConfigureAwait(false);
44+
45+
return deletedCount;
4246
}
4347

4448
private Task<bool> ExecuteAddWithTagsAsync(

src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.cs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
using System;
44
using System.Collections.Generic;
5-
using System.Globalization;
65
using System.Linq;
76
using System.Runtime.CompilerServices;
87
using System.Runtime.InteropServices;
@@ -482,25 +481,15 @@ public async Task<IEnumerable<string>> SearchKeysAsync(string pattern)
482481
pattern = $"{keyPrefix}{pattern}";
483482
var keys = new HashSet<string>();
484483

485-
foreach (var unused in ServerIteratorFactory.GetServers(connectionPoolManager.GetConnection(), serverEnumerationStrategy))
484+
foreach (var server in ServerIteratorFactory.GetServers(connectionPoolManager.GetConnection(), serverEnumerationStrategy))
486485
{
487-
ulong nextCursor = 0;
488-
do
489-
{
490-
var redisResult = await unused.ExecuteAsync("SCAN", nextCursor.ToString(CultureInfo.InvariantCulture), "MATCH", pattern, "COUNT", "1000").ConfigureAwait(false);
491-
var innerResult = (RedisResult[])redisResult!;
492-
493-
nextCursor = ulong.Parse((string)innerResult[0]!, CultureInfo.InvariantCulture);
494-
495-
var resultLines = ((string[])innerResult[1]!).ToArray();
496-
keys.UnionWith(resultLines);
497-
}
498-
while (nextCursor != 0);
486+
await foreach (var key in server.KeysAsync(dbNumber, pattern, 1000).ConfigureAwait(false))
487+
keys.Add(key!);
499488
}
500489

501490
return !string.IsNullOrEmpty(keyPrefix)
502-
? keys.Select(k => k[keyPrefix.Length..])
503-
: keys;
491+
? keys.Select(k => k.ToString()[keyPrefix.Length..])
492+
: keys.Select(k => k.ToString());
504493
}
505494

506495
/// <inheritdoc/>

tests/StackExchange.Redis.Extensions.Core.Tests/CacheClientTestBase.Tags.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,25 @@ public async Task RemoveByTagAsync_ShouldReturnOneDeletedValue_Async()
212212

213213
Assert.Equal(1, result);
214214
}
215+
216+
[Fact]
217+
[Trait("Category", "Tags")]
218+
public async Task RemoveByTagAsync_ShouldDeleteTagKey_Async()
219+
{
220+
const string testKey = "test_key";
221+
const string testValue = "test_value";
222+
var testClass = new Helpers.TestClass<string>(testKey, testValue);
223+
const string testTag = "test_tag";
224+
225+
await Sut.GetDefaultDatabase().AddAsync(testKey, testClass, tags: [testTag]);
226+
227+
var tagKeyName = TagHelper.GenerateTagKey(testTag);
228+
var tagExistsBefore = await db.KeyExistsAsync(tagKeyName);
229+
Assert.True(tagExistsBefore);
230+
231+
await Sut.GetDefaultDatabase().RemoveByTagAsync(testTag);
232+
233+
var tagExistsAfter = await db.KeyExistsAsync(tagKeyName);
234+
Assert.False(tagExistsAfter);
235+
}
215236
}

tests/StackExchange.Redis.Extensions.Core.Tests/CacheClientTestBase.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,31 @@ public async Task SearchKeys_With_Key_Prefix_Should_Return_Keys_Without_Prefix_A
338338
Assert.Equal(keys[i], values[i].Key);
339339
}
340340

341+
[Fact]
342+
public async Task SearchKeysAsync_Should_Respect_Database_Number_Async()
343+
{
344+
var db1 = Sut.Db1;
345+
var db1Raw = db1.Database;
346+
347+
try
348+
{
349+
await db1Raw.StringSetAsync("db1_key1", "value1");
350+
await db1Raw.StringSetAsync("db1_key2", "value2");
351+
352+
await db.StringSetAsync("db0_key1", serializer.Serialize("value_db0"));
353+
354+
var keysFromDb1 = (await db1.SearchKeysAsync("db1_*")).ToList();
355+
356+
Assert.Equal(2, keysFromDb1.Count);
357+
Assert.Contains("db1_key1", keysFromDb1);
358+
Assert.Contains("db1_key2", keysFromDb1);
359+
}
360+
finally
361+
{
362+
await db1Raw.ExecuteAsync("FLUSHDB");
363+
}
364+
}
365+
341366
[Fact]
342367
public async Task Exist_With_Valid_Object_Should_Return_The_Correct_Instance_Async()
343368
{

0 commit comments

Comments
 (0)