Skip to content

Commit 4ab6d2a

Browse files
committed
Core - ConcurrentMethodRunnerQueue.Enqueue throws InvalidOperationException
When ConcurrentMethodRunnerQueue.Dispose is called from a different thread it appears that the CancellationTokenSource is cancelled (via Dispose) inbetween creation of the Task and calling Task.Start(TaskScheduler). Switch from using Task constructor and Task.Start to the preferred Task.Run method. Added additional unit tests. Issue #3639
1 parent 445fb9a commit 4ab6d2a

File tree

2 files changed

+77
-4
lines changed

2 files changed

+77
-4
lines changed

CefSharp.Test/Framework/ConcurrentMethodRunnerQueueFacts.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//
33
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
44

5+
using System;
56
using System.Threading;
67
using System.Threading.Tasks;
78
using CefSharp.Example.JavascriptBinding;
@@ -21,6 +22,74 @@ public ConcurrentMethodRunnerQueueFacts(ITestOutputHelper output)
2122
this.output = output;
2223
}
2324

25+
/// <summary>
26+
/// Recreate the workflow that would appear to be the root cause of
27+
/// https://github.com/cefsharp/CefSharp/discussions/3638
28+
/// </summary>
29+
[Fact]
30+
public void SimulateStartOnTaskAlreadyCompleted()
31+
{
32+
var cts = new CancellationTokenSource();
33+
34+
//Create a new Task
35+
var task = new Task(async () =>
36+
{
37+
await Task.Delay(100);
38+
39+
}, cts.Token);
40+
41+
//Cancel before started
42+
cts.Cancel();
43+
44+
Assert.Throws<InvalidOperationException>(() => task.Start(TaskScheduler.Default));
45+
}
46+
47+
/// <summary>
48+
/// Proposed fix for
49+
/// https://github.com/cefsharp/CefSharp/discussions/3638
50+
/// It's difficult to recreate the exact threading requirements, so simulating the
51+
/// behaviour to test code executes without exception.
52+
/// </summary>
53+
[Fact]
54+
public void SimulateTaskRunStartOnTaskAlreadyCompleted()
55+
{
56+
var cts = new CancellationTokenSource();
57+
58+
//Cancel before started
59+
cts.Cancel();
60+
61+
var task = Task.Run(async () =>
62+
{
63+
await Task.Delay(100);
64+
}, cts.Token);
65+
66+
Assert.NotNull(task);
67+
Assert.Equal(TaskStatus.Canceled, task.Status);
68+
}
69+
70+
[Fact]
71+
public void DisposeConcurrentMethodRunnerQueueThenEnqueueInvocation()
72+
{
73+
var methodInvocation = new MethodInvocation(1, 1, 1, "Testing", 1);
74+
methodInvocation.Parameters.Add("Echo Me!");
75+
76+
var objectRepository = new JavascriptObjectRepository
77+
{
78+
NameConverter = null
79+
};
80+
81+
var methodRunnerQueue = new ConcurrentMethodRunnerQueue(objectRepository);
82+
83+
//Dispose
84+
methodRunnerQueue.Dispose();
85+
86+
//Enqueue
87+
var ex = Record.Exception(() => methodRunnerQueue.Enqueue(methodInvocation));
88+
89+
//Ensure no exception thrown
90+
Assert.Null(ex);
91+
}
92+
2493
[Fact]
2594
public async Task StopConcurrentMethodRunnerQueueWhenMethodRunning()
2695
{

CefSharp/Internals/ConcurrentMethodRunnerQueue.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,19 @@ public void Dispose()
3434

3535
public void Enqueue(MethodInvocation methodInvocation)
3636
{
37-
if(cancellationTokenSource.IsCancellationRequested)
37+
if (cancellationTokenSource.IsCancellationRequested)
3838
{
3939
return;
4040
}
4141

42-
var task = new Task(async () =>
42+
//Enqueue on ThreadPool
43+
Task.Run(async () =>
4344
{
45+
if (cancellationTokenSource.IsCancellationRequested)
46+
{
47+
return;
48+
}
49+
4450
var result = await ExecuteMethodInvocation(methodInvocation).ConfigureAwait(false);
4551

4652
if (cancellationTokenSource.IsCancellationRequested)
@@ -113,8 +119,6 @@ public void Enqueue(MethodInvocation methodInvocation)
113119
}
114120

115121
}, cancellationTokenSource.Token);
116-
117-
task.Start(TaskScheduler.Default);
118122
}
119123

120124
private async Task<MethodInvocationResult> ExecuteMethodInvocation(MethodInvocation methodInvocation)

0 commit comments

Comments
 (0)