diff --git a/src/Farfetch.LoadShedding/Limiters/AdaptativeConcurrencyLimiter.cs b/src/Farfetch.LoadShedding/Limiters/AdaptativeConcurrencyLimiter.cs index e234872..aa9e7b6 100644 --- a/src/Farfetch.LoadShedding/Limiters/AdaptativeConcurrencyLimiter.cs +++ b/src/Farfetch.LoadShedding/Limiters/AdaptativeConcurrencyLimiter.cs @@ -59,11 +59,13 @@ public Task ExecuteAsync(Func function, CancellationToken cancellationToke /// public async Task ExecuteAsync(Priority priority, Func function, CancellationToken cancellationToken = default) { + using (var delayTaskCancellationSource = new CancellationTokenSource()) + using (var linkedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, delayTaskCancellationSource.Token)) using (var item = await _taskManager.AcquireAsync(priority, cancellationToken)) { try { - var delayTask = Task.Delay(Timeout.Infinite, cancellationToken); + var delayTask = Task.Delay(Timeout.Infinite, linkedCancellationSource.Token); var resultTask = await Task.WhenAny( function.Invoke(), @@ -73,6 +75,14 @@ public async Task ExecuteAsync(Priority priority, Func function, Cancellat { cancellationToken.ThrowIfCancellationRequested(); } + else + { + // Stop delayTask, otherwise it is kept indefinetly and leaks CancellationTokenRegistration (DelayPromiseWithCancellation) + delayTaskCancellationSource.Cancel(); + + // await ensures cancellation was received and accepted before we dispose CancellationTokenSource + await delayTask.IgnoreWhenCancelled(); + } if (resultTask.IsFaulted && resultTask.Exception is not null) { diff --git a/src/Farfetch.LoadShedding/Tasks/TaskExtensions.cs b/src/Farfetch.LoadShedding/Tasks/TaskExtensions.cs new file mode 100644 index 0000000..1468c67 --- /dev/null +++ b/src/Farfetch.LoadShedding/Tasks/TaskExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; + +namespace Farfetch.LoadShedding.Tasks +{ + internal static class TaskExtensions + { + public static async Task IgnoreWhenCancelled(this Task task) + { + if (!task.IsCanceled) + { + try + { + await task.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } + } + } + } +}