From 1636ad051caf575fba535b7b18c8bad5e2f64cf1 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Mon, 5 Oct 2026 16:13:18 -0500 Subject: [PATCH 01/12] Round-trip GroupId in Redis queue envelope Depends on https://github.com/FoundatioFx/Foundatio/pull/579 --- src/Foundatio.Redis/Queues/RedisQueue.cs | 6 ++++-- .../Foundatio.Redis.Tests/Queues/RedisQueueTests.cs | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Foundatio.Redis/Queues/RedisQueue.cs b/src/Foundatio.Redis/Queues/RedisQueue.cs index 030eca6..af63d5e 100644 --- a/src/Foundatio.Redis/Queues/RedisQueue.cs +++ b/src/Foundatio.Redis/Queues/RedisQueue.cs @@ -218,6 +218,7 @@ private string GetTopicName() { Properties = options.Properties, CorrelationId = options.CorrelationId, + GroupId = options.GroupId, Value = data }; bool success = await _resiliencePolicy.ExecuteAsync(async _ => await _cache.AddAsync(GetPayloadKey(id), envelope, _payloadTimeToLive)).AnyContext(); @@ -240,7 +241,7 @@ await _resiliencePolicy.ExecuteAsync(async _ => await Task.WhenAll( } Interlocked.Increment(ref _enqueuedCount); - var entry = new QueueEntry(id, options.CorrelationId, data, this, now, 0); + var entry = new QueueEntry(id, options.CorrelationId, data, this, now, 0) { GroupId = options.GroupId }; await OnEnqueuedAsync(entry).AnyContext(); _logger.LogTrace("Enqueue done"); @@ -406,7 +407,7 @@ public override async Task RenewLockAsync(IQueueEntry entry) var attemptsValue = _resiliencePolicy.ExecuteAsync(async _ => await _cache.GetAsync(GetAttemptsKey(workId), 0)).AsTask(); await Task.WhenAll(enqueuedTimeTicks, attemptsValue).AnyContext(); - var queueEntry = new QueueEntry(workId, payload.Value.CorrelationId, payload.Value.Value, this, new DateTime(enqueuedTimeTicks.Result, DateTimeKind.Utc), attemptsValue.Result + 1); + var queueEntry = new QueueEntry(workId, payload.Value.CorrelationId, payload.Value.Value, this, new DateTime(enqueuedTimeTicks.Result, DateTimeKind.Utc), attemptsValue.Result + 1) { GroupId = payload.Value.GroupId }; if (payload.Value.Properties != null) { @@ -894,6 +895,7 @@ private void WaitForMaintenanceTask() public record RedisPayloadEnvelope { public string? CorrelationId { get; init; } + public string? GroupId { get; init; } public IDictionary? Properties { get; init; } public required T Value { get; init; } } diff --git a/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs b/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs index ed20ea1..2423655 100644 --- a/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs +++ b/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs @@ -120,6 +120,12 @@ public override Task DequeueAsync_AfterAbandonWithMutatedValue_ReturnsOriginalVa return base.DequeueAsync_AfterAbandonWithMutatedValue_ReturnsOriginalValueAsync(); } + [Fact] + public override Task AbandonAsync_WithGroupId_PreservesGroupIdOnRetryAsync() + { + return base.AbandonAsync_WithGroupId_PreservesGroupIdOnRetryAsync(); + } + [Fact] public override Task DequeueAsync_WithDispose_AutoAbandonsEntryAsync() { @@ -162,6 +168,12 @@ public override Task DuplicateDetection_WithNullIdentifier_AcceptsAllItemsAsync( return base.DuplicateDetection_WithNullIdentifier_AcceptsAllItemsAsync(); } + [Fact] + public override Task EnqueueAsync_WithGroupId_RoundTripsGroupIdAsync() + { + return base.EnqueueAsync_WithGroupId_RoundTripsGroupIdAsync(); + } + [Fact] public override Task EnqueueAsync_WithSerializationError_ThrowsAndLeavesQueueEmptyAsync() { From d6ad22369b7ecb53991ead5faa5010493c47cea0 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Mon, 5 Oct 2026 18:40:23 -0500 Subject: [PATCH 02/12] Register the empty GroupId queue regression --- tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs b/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs index 2423655..8cc2cb2 100644 --- a/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs +++ b/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs @@ -168,6 +168,12 @@ public override Task DuplicateDetection_WithNullIdentifier_AcceptsAllItemsAsync( return base.DuplicateDetection_WithNullIdentifier_AcceptsAllItemsAsync(); } + [Fact] + public override Task EnqueueAsync_WithEmptyGroupId_EnqueuesWithoutGroupAsync() + { + return base.EnqueueAsync_WithEmptyGroupId_EnqueuesWithoutGroupAsync(); + } + [Fact] public override Task EnqueueAsync_WithGroupId_RoundTripsGroupIdAsync() { From 2cb0b5ab58ac160bdd3f3fe5a56d51368802195b Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Mon, 5 Oct 2026 22:22:21 -0500 Subject: [PATCH 03/12] Pin guarded build workflow for feature branch validation --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c10208e..4dd0c32 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ on: [push, pull_request] jobs: build: - uses: FoundatioFx/Foundatio/.github/workflows/build-workflow.yml@main + uses: FoundatioFx/Foundatio/.github/workflows/build-workflow.yml@a28d0f736d9124431de09764c56aff77fbdc1d5d secrets: inherit with: new-test-runner: true From f8de9ba2de0c1d217eab461891c617d2365efdf1 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 6 Oct 2026 11:26:07 -0500 Subject: [PATCH 04/12] Consume the queue-group-id nightly of Foundatio and Foundatio.TestHarness The GroupId APIs are not in 13.0.4. Use the nightly until a release is available. --- src/Foundatio.Redis/Foundatio.Redis.csproj | 2 +- tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Foundatio.Redis/Foundatio.Redis.csproj b/src/Foundatio.Redis/Foundatio.Redis.csproj index 5c02167..ed090e1 100644 --- a/src/Foundatio.Redis/Foundatio.Redis.csproj +++ b/src/Foundatio.Redis/Foundatio.Redis.csproj @@ -1,7 +1,7 @@ - + diff --git a/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj b/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj index 8df2d05..3de79ff 100644 --- a/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj +++ b/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,7 +8,7 @@ - + From 672bcb79ed4f90bf0b3e42b04697bd83635b41ab Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 6 Oct 2026 16:43:17 -0500 Subject: [PATCH 05/12] Count and back off on worker dequeue errors The Redis worker loop logged dequeue errors and immediately looped again without counting them. It now counts them, ignores cancellation only when its own token is cancelled, and waits one second before retrying. Documents why the delayed-retry GroupId test is skipped: Redis requeues delayed retries in the minute-interval maintenance task. --- src/Foundatio.Redis/Queues/RedisQueue.cs | 8 ++++++++ .../Queues/RedisQueueTests.cs | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/Foundatio.Redis/Queues/RedisQueue.cs b/src/Foundatio.Redis/Queues/RedisQueue.cs index af63d5e..abc68e3 100644 --- a/src/Foundatio.Redis/Queues/RedisQueue.cs +++ b/src/Foundatio.Redis/Queues/RedisQueue.cs @@ -28,6 +28,7 @@ public class RedisQueue : QueueBase> where T : class private long _completedCount; private long _abandonedCount; private long _workerErrorCount; + private static readonly TimeSpan MinWorkerErrorDelay = TimeSpan.FromSeconds(1); private long _workItemTimeoutCount; private readonly ILockProvider _maintenanceLockProvider; private Task? _maintenanceTask; @@ -271,9 +272,16 @@ protected override void StartWorkingImpl(Func, CancellationToken, { queueEntry = await DequeueImplAsync(linkedCancellationTokenSource.Token).AnyContext(); } + catch (OperationCanceledException) when (linkedCancellationTokenSource.IsCancellationRequested) { } catch (Exception ex) { + Interlocked.Increment(ref _workerErrorCount); _logger.LogError(ex, "Error on Dequeue: {Message}", ex.Message); + try + { + await _timeProvider.Delay(MinWorkerErrorDelay, linkedCancellationTokenSource.Token).AnyContext(); + } + catch (OperationCanceledException) { } } if (linkedCancellationTokenSource.IsCancellationRequested || queueEntry == null) diff --git a/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs b/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs index 8cc2cb2..c361825 100644 --- a/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs +++ b/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs @@ -126,6 +126,12 @@ public override Task AbandonAsync_WithGroupId_PreservesGroupIdOnRetryAsync() return base.AbandonAsync_WithGroupId_PreservesGroupIdOnRetryAsync(); } + [Fact(Skip = "Redis requeues delayed retries in the minute-interval maintenance task")] + public override Task AbandonAsync_WithGroupIdAndRetryDelay_PreservesGroupIdOnRetryAsync() + { + return base.AbandonAsync_WithGroupIdAndRetryDelay_PreservesGroupIdOnRetryAsync(); + } + [Fact] public override Task DequeueAsync_WithDispose_AutoAbandonsEntryAsync() { @@ -168,6 +174,12 @@ public override Task DuplicateDetection_WithNullIdentifier_AcceptsAllItemsAsync( return base.DuplicateDetection_WithNullIdentifier_AcceptsAllItemsAsync(); } + [Fact] + public override Task EnqueueAsync_WhenEnqueuingHandlerClearsGroupId_EnqueuesWithoutGroupAsync() + { + return base.EnqueueAsync_WhenEnqueuingHandlerClearsGroupId_EnqueuesWithoutGroupAsync(); + } + [Fact] public override Task EnqueueAsync_WithEmptyGroupId_EnqueuesWithoutGroupAsync() { @@ -186,6 +198,12 @@ public override Task EnqueueAsync_WithSerializationError_ThrowsAndLeavesQueueEmp return base.EnqueueAsync_WithSerializationError_ThrowsAndLeavesQueueEmptyAsync(); } + [Fact] + public override Task EnqueueAsync_WithReusedOptions_DoesNotChangeCallerOptionsAsync() + { + return base.EnqueueAsync_WithReusedOptions_DoesNotChangeCallerOptionsAsync(); + } + [Fact] public override Task EnqueueAsync_WithUniqueId_UsesProvidedIdAsync() { From dec6b6080fd3112fafa5bf7316c62dd107cc6c66 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 8 Oct 2026 11:51:38 -0500 Subject: [PATCH 06/12] Restore main worker loop; keep GroupId plumbing only --- src/Foundatio.Redis/Queues/RedisQueue.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Foundatio.Redis/Queues/RedisQueue.cs b/src/Foundatio.Redis/Queues/RedisQueue.cs index abc68e3..af63d5e 100644 --- a/src/Foundatio.Redis/Queues/RedisQueue.cs +++ b/src/Foundatio.Redis/Queues/RedisQueue.cs @@ -28,7 +28,6 @@ public class RedisQueue : QueueBase> where T : class private long _completedCount; private long _abandonedCount; private long _workerErrorCount; - private static readonly TimeSpan MinWorkerErrorDelay = TimeSpan.FromSeconds(1); private long _workItemTimeoutCount; private readonly ILockProvider _maintenanceLockProvider; private Task? _maintenanceTask; @@ -272,16 +271,9 @@ protected override void StartWorkingImpl(Func, CancellationToken, { queueEntry = await DequeueImplAsync(linkedCancellationTokenSource.Token).AnyContext(); } - catch (OperationCanceledException) when (linkedCancellationTokenSource.IsCancellationRequested) { } catch (Exception ex) { - Interlocked.Increment(ref _workerErrorCount); _logger.LogError(ex, "Error on Dequeue: {Message}", ex.Message); - try - { - await _timeProvider.Delay(MinWorkerErrorDelay, linkedCancellationTokenSource.Token).AnyContext(); - } - catch (OperationCanceledException) { } } if (linkedCancellationTokenSource.IsCancellationRequested || queueEntry == null) From 0cc084fff3d951dd07eb86c02d5151604d103608 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 8 Oct 2026 14:05:51 -0500 Subject: [PATCH 07/12] Use matching GroupId core and TestHarness preview --- src/Foundatio.Redis/Foundatio.Redis.csproj | 2 +- tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Foundatio.Redis/Foundatio.Redis.csproj b/src/Foundatio.Redis/Foundatio.Redis.csproj index ed090e1..16fc17b 100644 --- a/src/Foundatio.Redis/Foundatio.Redis.csproj +++ b/src/Foundatio.Redis/Foundatio.Redis.csproj @@ -1,7 +1,7 @@ - + diff --git a/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj b/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj index 3de79ff..b946085 100644 --- a/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj +++ b/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj @@ -8,7 +8,7 @@ - + From d834d15f0a52da17b76783017eebe551294a541b Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 13 Oct 2026 14:22:06 -0500 Subject: [PATCH 08/12] Accept groupIdResolver in GetQueue, run base resolver tests, use core preview .0.21 --- src/Foundatio.Redis/Foundatio.Redis.csproj | 2 +- .../Foundatio.Redis.Tests.csproj | 2 +- .../Queues/RedisQueueTests.cs | 82 +++++++++++++++---- 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/src/Foundatio.Redis/Foundatio.Redis.csproj b/src/Foundatio.Redis/Foundatio.Redis.csproj index 16fc17b..5a8912c 100644 --- a/src/Foundatio.Redis/Foundatio.Redis.csproj +++ b/src/Foundatio.Redis/Foundatio.Redis.csproj @@ -1,7 +1,7 @@ - + diff --git a/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj b/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj index b946085..805cf9f 100644 --- a/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj +++ b/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs b/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs index c361825..b3cd892 100644 --- a/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs +++ b/tests/Foundatio.Redis.Tests/Queues/RedisQueueTests.cs @@ -30,25 +30,31 @@ public RedisQueueTests(ITestOutputHelper output) : base(output) { } - protected override IQueue? GetQueue(int retries = 1, TimeSpan? workItemTimeout = null, TimeSpan? retryDelay = null, int[]? retryMultipliers = null, int deadLetterMaxItems = 100, bool runQueueMaintenance = true, TimeProvider? timeProvider = null, ISerializer? serializer = null) + protected override IQueue? GetQueue(int retries = 1, TimeSpan? workItemTimeout = null, TimeSpan? retryDelay = null, int[]? retryMultipliers = null, int deadLetterMaxItems = 100, bool runQueueMaintenance = true, TimeProvider? timeProvider = null, ISerializer? serializer = null, Func? groupIdResolver = null) { var muxer = SharedConnection.GetMuxer(Log); if (muxer is null) return null; - var queue = new RedisQueue(o => o - .ConnectionMultiplexer(muxer) - .Retries(retries) - .RetryDelay(retryDelay.GetValueOrDefault(TimeSpan.FromMinutes(1))) - .RetryMultipliers(retryMultipliers ?? [1, 3, 5, 10]) - .DeadLetterMaxItems(deadLetterMaxItems) - .TimeProvider(timeProvider ?? TimeProvider.System) - .WorkItemTimeout(workItemTimeout.GetValueOrDefault(TimeSpan.FromMinutes(5))) - .MetricsPollingInterval(TimeSpan.Zero) - .RunMaintenanceTasks(runQueueMaintenance) - .Serializer(serializer) - .LoggerFactory(Log) - ); + var queue = new RedisQueue(o => + { + o.ConnectionMultiplexer(muxer) + .Retries(retries) + .RetryDelay(retryDelay.GetValueOrDefault(TimeSpan.FromMinutes(1))) + .RetryMultipliers(retryMultipliers ?? [1, 3, 5, 10]) + .DeadLetterMaxItems(deadLetterMaxItems) + .TimeProvider(timeProvider ?? TimeProvider.System) + .WorkItemTimeout(workItemTimeout.GetValueOrDefault(TimeSpan.FromMinutes(5))) + .MetricsPollingInterval(TimeSpan.Zero) + .RunMaintenanceTasks(runQueueMaintenance) + .Serializer(serializer) + .LoggerFactory(Log); + + if (groupIdResolver is not null) + o.GroupId(groupIdResolver); + + return o; + }); _logger.LogDebug("Queue Id: {QueueId}", queue.QueueId); return queue; @@ -204,6 +210,54 @@ public override Task EnqueueAsync_WithReusedOptions_DoesNotChangeCallerOptionsAs return base.EnqueueAsync_WithReusedOptions_DoesNotChangeCallerOptionsAsync(); } + [Fact] + public override Task EnqueueAsync_WhenResolverReturnsEmpty_EnqueuesWithoutGroupAsync() + { + return base.EnqueueAsync_WhenResolverReturnsEmpty_EnqueuesWithoutGroupAsync(); + } + + [Fact] + public override Task EnqueueAsync_WhenResolverReturnsNull_EnqueuesWithoutGroupAsync() + { + return base.EnqueueAsync_WhenResolverReturnsNull_EnqueuesWithoutGroupAsync(); + } + + [Fact] + public override Task EnqueueAsync_WithActivity_AppliesCorrelationBeforeEnqueuingHandlersAsync() + { + return base.EnqueueAsync_WithActivity_AppliesCorrelationBeforeEnqueuingHandlersAsync(); + } + + [Fact] + public override Task EnqueueAsync_WithEmptyGroupIdAndResolver_UsesPayloadValueAsync() + { + return base.EnqueueAsync_WithEmptyGroupIdAndResolver_UsesPayloadValueAsync(); + } + + [Fact] + public override Task EnqueueAsync_WithExplicitGroupId_OverridesResolverAsync() + { + return base.EnqueueAsync_WithExplicitGroupId_OverridesResolverAsync(); + } + + [Fact] + public override Task EnqueueAsync_WithGroupIdResolver_DoesNotChangeCallerOptionsAsync() + { + return base.EnqueueAsync_WithGroupIdResolver_DoesNotChangeCallerOptionsAsync(); + } + + [Fact] + public override Task EnqueueAsync_WithGroupIdResolver_UsesPayloadValueAsync() + { + return base.EnqueueAsync_WithGroupIdResolver_UsesPayloadValueAsync(); + } + + [Fact] + public override Task EnqueueAsync_WithReusedOptionsAndGroupIdResolver_EnqueuesEachMessageAsync() + { + return base.EnqueueAsync_WithReusedOptionsAndGroupIdResolver_EnqueuesEachMessageAsync(); + } + [Fact] public override Task EnqueueAsync_WithUniqueId_UsesProvidedIdAsync() { From 9f56c746113df12efd01955cdf523114bd25afe7 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 13 Oct 2026 21:11:18 -0500 Subject: [PATCH 09/12] Update Foundatio to 13.0.5-preview.0.10 and point build workflow back to main --- .github/workflows/build.yml | 2 +- .../Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj | 2 +- src/Foundatio.Redis/Foundatio.Redis.csproj | 2 +- tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4dd0c32..c10208e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ on: [push, pull_request] jobs: build: - uses: FoundatioFx/Foundatio/.github/workflows/build-workflow.yml@a28d0f736d9124431de09764c56aff77fbdc1d5d + uses: FoundatioFx/Foundatio/.github/workflows/build-workflow.yml@main secrets: inherit with: new-test-runner: true diff --git a/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj b/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj index 2e598b5..28006c1 100644 --- a/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj +++ b/samples/Foundatio.SampleJobClient/Foundatio.SampleJobClient.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Foundatio.Redis/Foundatio.Redis.csproj b/src/Foundatio.Redis/Foundatio.Redis.csproj index 5a8912c..410eb4a 100644 --- a/src/Foundatio.Redis/Foundatio.Redis.csproj +++ b/src/Foundatio.Redis/Foundatio.Redis.csproj @@ -1,7 +1,7 @@ - + diff --git a/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj b/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj index 805cf9f..338768f 100644 --- a/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj +++ b/tests/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj @@ -8,7 +8,7 @@ - + From dde7d1d9de35f486cc04e661a1ad6e592d148dc2 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 13 Oct 2026 22:10:30 -0500 Subject: [PATCH 10/12] Remove archived Minio provider from README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index b63266a..a8a83f0 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,6 @@ await storage.SaveFileAsync("docs/readme.txt", "Hello World"); | [AWS (S3/SQS/SNS)](https://github.com/FoundatioFx/Foundatio.AWS) | | ✅ | ✅ | ✅ | | | [RabbitMQ](https://github.com/FoundatioFx/Foundatio.RabbitMQ) | | | ✅ | | | | [Kafka](https://github.com/FoundatioFx/Foundatio.Kafka) | | | ✅ | | | -| [Minio](https://github.com/FoundatioFx/Foundatio.Minio) | | | | ✅ | | | [Aliyun](https://github.com/FoundatioFx/Foundatio.Aliyun) | | | | ✅ | | | [SFTP](https://github.com/FoundatioFx/Foundatio.Storage.SshNet) | | | | ✅ | | From 2f3ff484dbbd90b35d139b4e6ac837c76fb22a4a Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 13 Oct 2026 23:05:31 -0500 Subject: [PATCH 11/12] Retry test flush after refreshing cluster topology when a cached primary became a replica --- .../ConnectionMultiplexerExtensions.cs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/Foundatio.Redis.Tests/Extensions/ConnectionMultiplexerExtensions.cs b/tests/Foundatio.Redis.Tests/Extensions/ConnectionMultiplexerExtensions.cs index ec0ba2e..9fdc105 100644 --- a/tests/Foundatio.Redis.Tests/Extensions/ConnectionMultiplexerExtensions.cs +++ b/tests/Foundatio.Redis.Tests/Extensions/ConnectionMultiplexerExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Threading.Tasks; using StackExchange.Redis; @@ -8,9 +9,17 @@ public static class ConnectionMultiplexerExtensions { public static async Task FlushAllAsync(this ConnectionMultiplexer muxer) { - int database = muxer.GetDatabase().Database; - foreach (var server in muxer.GetEndPoints().Select(ep => muxer.GetServer(ep)).Where(s => !s.IsReplica)) - await server.FlushDatabaseAsync(database); + try + { + await FlushPrimariesAsync(muxer); + } + catch (RedisException ex) when (IsReplicaError(ex)) + { + // Cluster roles can change after the multiplexer cached them (for example while the cluster settles at startup). + // Refresh the topology so the current primaries are flushed instead of a node that became a replica. + await muxer.ConfigureAsync(); + await FlushPrimariesAsync(muxer); + } } public static async Task CountAllKeysAsync(this ConnectionMultiplexer muxer) @@ -22,4 +31,17 @@ public static async Task CountAllKeysAsync(this ConnectionMultiplexer muxe return count; } -} + + private static async Task FlushPrimariesAsync(ConnectionMultiplexer muxer) + { + int database = muxer.GetDatabase().Database; + foreach (var server in muxer.GetEndPoints().Select(ep => muxer.GetServer(ep)).Where(s => !s.IsReplica)) + await server.FlushDatabaseAsync(database); + } + + private static bool IsReplicaError(RedisException ex) + { + return ex.Message.Contains("replica", StringComparison.OrdinalIgnoreCase) + || ex.InnerException?.Message.Contains("replica", StringComparison.OrdinalIgnoreCase) is true; + } +} \ No newline at end of file From 5c1d3b70929667c49140aff93c768da183a9ba2f Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 13 Oct 2026 23:06:17 -0500 Subject: [PATCH 12/12] Format --- .../Extensions/ConnectionMultiplexerExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Foundatio.Redis.Tests/Extensions/ConnectionMultiplexerExtensions.cs b/tests/Foundatio.Redis.Tests/Extensions/ConnectionMultiplexerExtensions.cs index 9fdc105..f1c8a18 100644 --- a/tests/Foundatio.Redis.Tests/Extensions/ConnectionMultiplexerExtensions.cs +++ b/tests/Foundatio.Redis.Tests/Extensions/ConnectionMultiplexerExtensions.cs @@ -44,4 +44,4 @@ private static bool IsReplicaError(RedisException ex) return ex.Message.Contains("replica", StringComparison.OrdinalIgnoreCase) || ex.InnerException?.Message.Contains("replica", StringComparison.OrdinalIgnoreCase) is true; } -} \ No newline at end of file +}