From aa841d58a82896d8e3bbad4bcb4882b8382550e7 Mon Sep 17 00:00:00 2001 From: Ciaran Harvey Date: Tue, 3 Mar 2026 16:53:36 +0000 Subject: [PATCH 1/3] Add failing test for inline continuation deadlock --- .../Controller/ScheduledTaskExecutorTests.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs b/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs index 27c854ab40d..8527e0de3ce 100644 --- a/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs +++ b/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs @@ -129,6 +129,27 @@ public void Dispose_WhenScheduledTaskExecuting() #pragma warning restore xUnit1031 } + [Fact] + public void ScheduleTask_InlineContinuationDoesNotDeadlock() + { + var timeout = TimeSpan.FromMilliseconds(250); + using var scheduler = new ScheduledTaskExecutor(); + + var firstTask = scheduler.ScheduleTask(() => Task.CompletedTask); + + var continuationTask = firstTask.ContinueWith( + _ => + { + var secondTask = scheduler.ScheduleTask(() => Task.CompletedTask); + secondTask.Wait(timeout); + }, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + + continuationTask.Wait(timeout).ShouldBeTrue(); + } + [Fact] public async Task Dispose_EnsureNoBackgroundProcessing() { From e71c0b792b510a33c5ffd0c15a75e71cbb3c6d42 Mon Sep 17 00:00:00 2001 From: Ciaran Harvey Date: Tue, 3 Mar 2026 16:54:05 +0000 Subject: [PATCH 2/3] Make sure we run task continuations async --- .../CircuitBreaker/Controller/ScheduledTaskExecutor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Polly.Core/CircuitBreaker/Controller/ScheduledTaskExecutor.cs b/src/Polly.Core/CircuitBreaker/Controller/ScheduledTaskExecutor.cs index f23e450e7cd..1fdfeea4d65 100644 --- a/src/Polly.Core/CircuitBreaker/Controller/ScheduledTaskExecutor.cs +++ b/src/Polly.Core/CircuitBreaker/Controller/ScheduledTaskExecutor.cs @@ -26,7 +26,7 @@ public Task ScheduleTask(Func taskFactory) } #endif - var source = new TaskCompletionSource(); + var source = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _tasks.Enqueue(new Entry(taskFactory, source)); _semaphore.Release(); From 7e93bafe9983684189a8bfcc18bad9f04d1fae7d Mon Sep 17 00:00:00 2001 From: Ciaran Harvey Date: Tue, 3 Mar 2026 18:21:10 +0000 Subject: [PATCH 3/3] Use the xunit test cancellation token --- .../CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs b/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs index 8527e0de3ce..58e7faf1fd4 100644 --- a/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs +++ b/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs @@ -143,7 +143,7 @@ public void ScheduleTask_InlineContinuationDoesNotDeadlock() var secondTask = scheduler.ScheduleTask(() => Task.CompletedTask); secondTask.Wait(timeout); }, - CancellationToken.None, + CancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);