Skip to content

Commit 11cfb7d

Browse files
committed
Review Callback classes and implement the simplest possible fix until a long term solution for #2349 is devised
- Add OnComplete check to all the TaskCompletionSource based callbacks that could potentially have executed TrySetResultAsync twice, which could on rare occasions execute in a non deterministic fashion (The Disposed execution of TrySetResultAsync called before the OnComplete one). See #2349 for a more detailed proposal and one potential long term solution (upgrading to .Net 4.6 is the best option, though not practical now, will update the example projects shortly). - Update callbacks for consistency, all now implement IDisposable and have an IsDisposed
1 parent 22b299e commit 11cfb7d

7 files changed

+78
-47
lines changed

CefSharp.Core/Internals/CefResolveCallbackAdapter.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ namespace CefSharp
2222

2323
~CefResolveCallbackAdapter()
2424
{
25-
_handler = nullptr;
25+
delete _handler;
26+
_handler = nullptr;
2627
}
2728

2829
void OnResolveCompleted(cef_errorcode_t result, const std::vector<CefString>& resolvedIps) OVERRIDE

CefSharp/Callback/TaskCompletionCallback.cs

+12-6
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@ namespace CefSharp
1010
{
1111
public class TaskCompletionCallback : ICompletionCallback
1212
{
13-
private readonly TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
13+
private readonly TaskCompletionSource<bool> taskCompletionSource;
1414
private volatile bool isDisposed;
15-
private bool complete; //Only ever accessed on the same CEF thread, so no need for thread safety
15+
private bool onComplete; //Only ever accessed on the same CEF thread, so no need for thread safety
16+
17+
public TaskCompletionCallback()
18+
{
19+
taskCompletionSource = new TaskCompletionSource<bool>();
20+
}
1621

1722
void ICompletionCallback.OnComplete()
1823
{
19-
complete = true;
24+
onComplete = true;
2025

2126
taskCompletionSource.TrySetResultAsync(true);
2227
}
@@ -35,9 +40,10 @@ void IDisposable.Dispose()
3540
{
3641
var task = taskCompletionSource.Task;
3742

38-
//If the Task hasn't completed and this is being disposed then
39-
//set the TCS to false
40-
if(complete == false && task.IsCompleted == false)
43+
//If onComplete is false then ICompletionCallback.OnComplete was never called,
44+
//so we'll set the result to false. Calling TrySetResultAsync multiple times
45+
//can result in the issue outlined in https://github.com/cefsharp/CefSharp/pull/2349
46+
if (onComplete == false && task.IsCompleted == false)
4147
{
4248
taskCompletionSource.TrySetResultAsync(false);
4349
}

CefSharp/Callback/TaskDeleteCookiesCallback.cs

+12-7
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@ public class TaskDeleteCookiesCallback : IDeleteCookiesCallback
1515
{
1616
public const int InvalidNoOfCookiesDeleted = -1;
1717

18-
private readonly TaskCompletionSource<int> taskCompletionSource = new TaskCompletionSource<int>();
18+
private readonly TaskCompletionSource<int> taskCompletionSource;
1919
private volatile bool isDisposed;
20-
private bool complete; //Only ever accessed on the same CEF thread, so no need for thread safety
20+
private bool onComplete; //Only ever accessed on the same CEF thread, so no need for thread safety
21+
22+
public TaskDeleteCookiesCallback()
23+
{
24+
taskCompletionSource = new TaskCompletionSource<int>();
25+
}
2126

2227
void IDeleteCookiesCallback.OnComplete(int numDeleted)
2328
{
24-
complete = true;
29+
onComplete = true;
2530

2631
taskCompletionSource.TrySetResultAsync(numDeleted);
2732
}
@@ -35,14 +40,14 @@ bool IDeleteCookiesCallback.IsDisposed
3540
{
3641
get { return isDisposed; }
3742
}
38-
3943
void IDisposable.Dispose()
4044
{
4145
var task = taskCompletionSource.Task;
4246

43-
//If the Task hasn't completed and this is being disposed then
44-
//set the TCS to InvalidNoOfCookiesDeleted
45-
if (complete == false && task.IsCompleted == false)
47+
//If onComplete is false then IDeleteCookiesCallback.OnComplete was never called,
48+
//so we'll set the result to false. Calling TrySetResultAsync multiple times
49+
//can result in the issue outlined in https://github.com/cefsharp/CefSharp/pull/2349
50+
if (onComplete == false && task.IsCompleted == false)
4651
{
4752
taskCompletionSource.TrySetResultAsync(InvalidNoOfCookiesDeleted);
4853
}

CefSharp/Callback/TaskPrintToPdfCallback.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public sealed class TaskPrintToPdfCallback : IPrintToPdfCallback
1212
{
1313
private readonly TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
1414
private volatile bool isDisposed;
15-
private bool complete; //Only ever accessed on the same CEF thread, so no need for thread safety
15+
private bool onComplete; //Only ever accessed on the same CEF thread, so no need for thread safety
1616

1717
public Task<bool> Task
1818
{
@@ -21,7 +21,7 @@ public Task<bool> Task
2121

2222
void IPrintToPdfCallback.OnPdfPrintFinished(string path, bool ok)
2323
{
24-
complete = true;
24+
onComplete = true;
2525

2626
taskCompletionSource.TrySetResultAsync(ok);
2727
}
@@ -35,9 +35,10 @@ void IDisposable.Dispose()
3535
{
3636
var task = taskCompletionSource.Task;
3737

38-
//If the Task hasn't completed and this is being disposed then
39-
//set the TCS to false
40-
if (complete == false && task.IsCompleted == false)
38+
//If onComplete is false then IPrintToPdfCallback.OnPdfPrintFinished was never called,
39+
//so we'll set the result to false. Calling TrySetResultAsync multiple times
40+
//can result in the issue outlined in https://github.com/cefsharp/CefSharp/pull/2349
41+
if (onComplete == false && task.IsCompleted == false)
4142
{
4243
taskCompletionSource.TrySetResultAsync(false);
4344
}

CefSharp/Callback/TaskRegisterCdmCallback.cs

+12-6
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@ namespace CefSharp
1313
/// </summary>
1414
public class TaskRegisterCdmCallback : IRegisterCdmCallback
1515
{
16-
private readonly TaskCompletionSource<CdmRegistration> taskCompletionSource = new TaskCompletionSource<CdmRegistration>();
16+
private readonly TaskCompletionSource<CdmRegistration> taskCompletionSource;
1717
private volatile bool isDisposed;
18-
private bool complete; //Only ever accessed on the same CEF thread, so no need for thread safety
18+
private bool onComplete; //Only ever accessed on the same CEF thread, so no need for thread safety
19+
20+
public TaskRegisterCdmCallback()
21+
{
22+
taskCompletionSource = new TaskCompletionSource<CdmRegistration>();
23+
}
1924

2025
void IRegisterCdmCallback.OnRegistrationComplete(CdmRegistration registration)
2126
{
22-
complete = true;
27+
onComplete = true;
2328

2429
taskCompletionSource.TrySetResultAsync(registration);
2530
}
@@ -38,9 +43,10 @@ void IDisposable.Dispose()
3843
{
3944
var task = taskCompletionSource.Task;
4045

41-
//If the Task hasn't completed and this is being disposed then
42-
//set the TCS to null
43-
if (complete == false && task.IsCompleted == false)
46+
//If onComplete is false then IRegisterCdmCallback.OnRegistrationComplete was never called,
47+
//so we'll set the result to false. Calling TrySetResultAsync multiple times
48+
//can result in the issue outlined in https://github.com/cefsharp/CefSharp/pull/2349
49+
if (onComplete == false && task.IsCompleted == false)
4450
{
4551
taskCompletionSource.TrySetResultAsync(null);
4652
}

CefSharp/Callback/TaskResolveCallback.cs

+22-16
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,45 @@ namespace CefSharp
1111
{
1212
public class TaskResolveCallback : IResolveCallback
1313
{
14-
private readonly TaskCompletionSource<ResolveCallbackResult> taskCompletionSource = new TaskCompletionSource<ResolveCallbackResult>();
14+
private readonly TaskCompletionSource<ResolveCallbackResult> taskCompletionSource;
1515
private volatile bool isDisposed;
16-
private bool complete; //Only ever accessed on the same CEF thread, so no need for thread safety
16+
private bool onComplete; //Only ever accessed on the same CEF thread, so no need for thread safety
17+
18+
public TaskResolveCallback()
19+
{
20+
taskCompletionSource = new TaskCompletionSource<ResolveCallbackResult>();
21+
}
1722

1823
void IResolveCallback.OnResolveCompleted(CefErrorCode result, IList<string> resolvedIpAddresses)
1924
{
20-
complete = true;
25+
onComplete = true;
2126

2227
taskCompletionSource.TrySetResultAsync(new ResolveCallbackResult(result, resolvedIpAddresses));
2328
}
2429

25-
void IDisposable.Dispose()
30+
bool IResolveCallback.IsDisposed
2631
{
27-
var task = taskCompletionSource.Task;
28-
29-
//If the Task hasn't completed and this is being disposed then
30-
//set the TCS to null
31-
if (complete == false && task.IsCompleted == false)
32-
{
33-
taskCompletionSource.TrySetResultAsync(new ResolveCallbackResult(CefErrorCode.Unexpected, null));
34-
}
35-
36-
isDisposed = true;
32+
get { return isDisposed; }
3733
}
3834

3935
public Task<ResolveCallbackResult> Task
4036
{
4137
get { return taskCompletionSource.Task; }
4238
}
4339

44-
bool IResolveCallback.IsDisposed
40+
void IDisposable.Dispose()
4541
{
46-
get { return isDisposed; }
42+
var task = taskCompletionSource.Task;
43+
44+
//If onComplete is false then IResolveCallback.OnResolveCompleted was never called,
45+
//so we'll set the result to false. Calling TrySetResultAsync multiple times
46+
//can result in the issue outlined in https://github.com/cefsharp/CefSharp/pull/2349
47+
if (onComplete == false && task.IsCompleted == false)
48+
{
49+
taskCompletionSource.TrySetResultAsync(new ResolveCallbackResult(CefErrorCode.Unexpected, null));
50+
}
51+
52+
isDisposed = true;
4753
}
4854
}
4955
}

CefSharp/Callback/TaskSetCookieCallback.cs

+12-6
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@ namespace CefSharp
1313
/// </summary>
1414
public class TaskSetCookieCallback : ISetCookieCallback
1515
{
16-
private readonly TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
16+
private readonly TaskCompletionSource<bool> taskCompletionSource;
1717
private volatile bool isDisposed;
18-
private bool complete; //Only ever accessed on the same CEF thread, so no need for thread safety
18+
private bool onComplete; //Only ever accessed on the same CEF thread, so no need for thread safety
19+
20+
public TaskSetCookieCallback()
21+
{
22+
taskCompletionSource = new TaskCompletionSource<bool>();
23+
}
1924

2025
void ISetCookieCallback.OnComplete(bool success)
2126
{
22-
complete = true;
27+
onComplete = true;
2328

2429
taskCompletionSource.TrySetResultAsync(success);
2530
}
@@ -38,9 +43,10 @@ void IDisposable.Dispose()
3843
{
3944
var task = taskCompletionSource.Task;
4045

41-
//If the Task hasn't completed and this is being disposed then
42-
//set the TCS to false
43-
if (complete == false && task.IsCompleted == false)
46+
//If onComplete is false then ISetCookieCallback.OnComplete was never called,
47+
//so we'll set the result to false. Calling TrySetResultAsync multiple times
48+
//can result in the issue outlined in https://github.com/cefsharp/CefSharp/pull/2349
49+
if (onComplete == false && task.IsCompleted == false)
4450
{
4551
taskCompletionSource.TrySetResultAsync(false);
4652
}

0 commit comments

Comments
 (0)