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(); diff --git a/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs b/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs index 27c854ab40d..58e7faf1fd4 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, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + + continuationTask.Wait(timeout).ShouldBeTrue(); + } + [Fact] public async Task Dispose_EnsureNoBackgroundProcessing() {