Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
24 changes: 15 additions & 9 deletions src/DistributedLock.Core/Internal/Data/ConnectionMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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()
Expand Down
8 changes: 8 additions & 0 deletions src/DistributedLock.Core/Internal/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ public static bool TryGetValue<T>(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
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/DistributedLock.Core/Internal/LeaseMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private static async Task<bool> RunMonitoringLoopIterationAsync(WeakReference<Le
}

// offload cancel to a background thread to avoid hangs or errors
void OnHandleLost() => monitor._cancellationTask = Task.Run(() => monitor._handleLostSource.Cancel());
void OnHandleLost() => monitor._cancellationTask = monitor._handleLostSource.CancelAsync();
}

private async Task<LeaseState> CheckLeaseAsync()
Expand Down
29 changes: 29 additions & 0 deletions src/DistributedLock.Tests/Tests/Core/HelpersTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,33 @@ async Task TryAwaitFailedTask()
await task.TryAwait();
}
}

[Test]
public async Task TestCancelAsyncDoesNotBlockOnCallbacks()
{
using var cts = new CancellationTokenSource();

var callbackStarted = new TaskCompletionSource<bool>(
TaskCreationOptions.RunContinuationsAsynchronously);

var releaseCallback = new TaskCompletionSource<bool>(
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;
}
}