Skip to content

Commit 016dd90

Browse files
authored
Fix ScheduledTaskExecutor deadlock when TrySetResult runs continuations inline (#2953)
* Add failing test for inline continuation deadlock * Make sure we run task continuations async Resolves #2948.
1 parent 779aa83 commit 016dd90

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

src/Polly.Core/CircuitBreaker/Controller/ScheduledTaskExecutor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public Task ScheduleTask(Func<Task> taskFactory)
2626
}
2727
#endif
2828

29-
var source = new TaskCompletionSource<object>();
29+
var source = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
3030

3131
_tasks.Enqueue(new Entry(taskFactory, source));
3232
_semaphore.Release();

test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,27 @@ public void Dispose_WhenScheduledTaskExecuting()
129129
#pragma warning restore xUnit1031
130130
}
131131

132+
[Fact]
133+
public void ScheduleTask_InlineContinuationDoesNotDeadlock()
134+
{
135+
var timeout = TimeSpan.FromMilliseconds(250);
136+
using var scheduler = new ScheduledTaskExecutor();
137+
138+
var firstTask = scheduler.ScheduleTask(() => Task.CompletedTask);
139+
140+
var continuationTask = firstTask.ContinueWith(
141+
_ =>
142+
{
143+
var secondTask = scheduler.ScheduleTask(() => Task.CompletedTask);
144+
secondTask.Wait(timeout);
145+
},
146+
CancellationToken,
147+
TaskContinuationOptions.ExecuteSynchronously,
148+
TaskScheduler.Default);
149+
150+
continuationTask.Wait(timeout).ShouldBeTrue();
151+
}
152+
132153
[Fact]
133154
public async Task Dispose_EnsureNoBackgroundProcessing()
134155
{

0 commit comments

Comments
 (0)