From 4562b5b650437967925738110c22fa6170394016 Mon Sep 17 00:00:00 2001 From: Solution Date: Wed, 12 Aug 2026 04:51:01 +0300 Subject: [PATCH 1/2] Add a CancellationTokenSource.CancelAsync shim --- .../Internal/Data/ConnectionMonitor.cs | 24 +++++++++------ src/DistributedLock.Core/Internal/Helpers.cs | 8 +++++ .../Internal/LeaseMonitor.cs | 2 +- .../Tests/Core/HelpersTest.cs | 29 +++++++++++++++++++ 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/DistributedLock.Core/Internal/Data/ConnectionMonitor.cs b/src/DistributedLock.Core/Internal/Data/ConnectionMonitor.cs index 41d796f5..97fe43cf 100644 --- a/src/DistributedLock.Core/Internal/Data/ConnectionMonitor.cs +++ b/src/DistributedLock.Core/Internal/Data/ConnectionMonitor.cs @@ -277,11 +277,7 @@ private void CloseOrCancelMonitoringHandleRegistrationsNoLock(bool isCancel) if (isCancel) { // cancel in a background thread in case we have hangs or errors - Task.Run(() => - { - try { cancellationTokenSource.Cancel(); } - finally { cancellationTokenSource.Dispose(); } - }); + _ = CancelAndDisposeAsync(cancellationTokenSource); } else { @@ -327,11 +323,21 @@ private void FireStateChangedNoLock() // it is still safer and easier to reason about not to have that happen. This also ensures // that FireStateChangedNoLock() always returns quickly, even if the monitoring loop // were to do some synchronous work on the continuation thread. - Task.Run(() => + _ = CancelAndDisposeAsync(monitorStateChangedTokenSource); + } + + private static async Task CancelAndDisposeAsync( + CancellationTokenSource cancellationTokenSource) + { + try { - try { monitorStateChangedTokenSource.Cancel(); } - finally { monitorStateChangedTokenSource.Dispose(); } - }); + await cancellationTokenSource.CancelAsync() + .ConfigureAwait(false); + } + finally + { + cancellationTokenSource.Dispose(); + } } private async Task MonitorWorkerLoop() diff --git a/src/DistributedLock.Core/Internal/Helpers.cs b/src/DistributedLock.Core/Internal/Helpers.cs index 84a2916b..26440053 100644 --- a/src/DistributedLock.Core/Internal/Helpers.cs +++ b/src/DistributedLock.Core/Internal/Helpers.cs @@ -106,6 +106,14 @@ public static bool TryGetValue(this T? nullable, out T value) value = nullable.GetValueOrDefault(); return nullable.HasValue; } + +#if !NET8_0_OR_GREATER + public static Task CancelAsync( + this CancellationTokenSource cancellationTokenSource) + { + return Task.Run(cancellationTokenSource.Cancel); + } +#endif } /// diff --git a/src/DistributedLock.Core/Internal/LeaseMonitor.cs b/src/DistributedLock.Core/Internal/LeaseMonitor.cs index 71549128..6b5107bf 100644 --- a/src/DistributedLock.Core/Internal/LeaseMonitor.cs +++ b/src/DistributedLock.Core/Internal/LeaseMonitor.cs @@ -107,7 +107,7 @@ private static async Task RunMonitoringLoopIterationAsync(WeakReference monitor._cancellationTask = Task.Run(() => monitor._handleLostSource.Cancel()); + void OnHandleLost() => monitor._cancellationTask = monitor._handleLostSource.CancelAsync(); } private async Task CheckLeaseAsync() diff --git a/src/DistributedLock.Tests/Tests/Core/HelpersTest.cs b/src/DistributedLock.Tests/Tests/Core/HelpersTest.cs index abd79610..3ef7a7ec 100644 --- a/src/DistributedLock.Tests/Tests/Core/HelpersTest.cs +++ b/src/DistributedLock.Tests/Tests/Core/HelpersTest.cs @@ -77,4 +77,33 @@ async Task TryAwaitFailedTask() await task.TryAwait(); } } + + [Test] + public async Task TestCancelAsyncDoesNotBlockOnCallbacks() + { + using var cts = new CancellationTokenSource(); + + var callbackStarted = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + + var releaseCallback = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + + using var registration = cts.Token.Register(() => + { + callbackStarted.SetResult(true); + + releaseCallback.Task.GetAwaiter().GetResult(); + }); + + var cancellationTask = cts.CancelAsync(); + + await callbackStarted.Task; + + Assert.That(cancellationTask.IsCompleted, Is.False); + + releaseCallback.SetResult(true); + + await cancellationTask; + } } From 185582b2c67330ea45f41fb7960770cca1059d9f Mon Sep 17 00:00:00 2001 From: Solution Date: Sun, 23 Aug 2026 22:43:35 +0300 Subject: [PATCH 2/2] Fix formatting --- .../Internal/Data/ConnectionMonitor.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/DistributedLock.Core/Internal/Data/ConnectionMonitor.cs b/src/DistributedLock.Core/Internal/Data/ConnectionMonitor.cs index 97fe43cf..90fdd147 100644 --- a/src/DistributedLock.Core/Internal/Data/ConnectionMonitor.cs +++ b/src/DistributedLock.Core/Internal/Data/ConnectionMonitor.cs @@ -329,15 +329,8 @@ private void FireStateChangedNoLock() private static async Task CancelAndDisposeAsync( CancellationTokenSource cancellationTokenSource) { - try - { - await cancellationTokenSource.CancelAsync() - .ConfigureAwait(false); - } - finally - { - cancellationTokenSource.Dispose(); - } + try { await cancellationTokenSource.CancelAsync().ConfigureAwait(false); } + finally { cancellationTokenSource.Dispose(); } } private async Task MonitorWorkerLoop()