Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 595d401

Browse files
committed
Fix race condition in AsyncOperation
The AsyncOperation type in System.ComponentModel.EventBasedAsync tracks whether it's already been completed, and any subsequent attempts to Post operations through a completed instance are supposed to result in exceptions. However, the implementation currently doesn't set its completion flag until after it's invoked the completion callback. This means that if the completion callback then itself attempts to reuse the instance or if it initiates another async operation that does, there's a race condition between the subsequent access to the instance and the setting of the flag. This change fixes up the ordering so that the flag is set prior to invoking the completion callback rather than after.
1 parent fac8a01 commit 595d401

File tree

1 file changed

+18
-5
lines changed

1 file changed

+18
-5
lines changed

src/System.ComponentModel.EventBasedAsync/src/System/ComponentModel/AsyncOperation.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,37 @@ public SynchronizationContext SynchronizationContext
5353

5454
public void Post(SendOrPostCallback d, object arg)
5555
{
56-
VerifyNotCompleted();
57-
VerifyDelegateNotNull(d);
58-
_syncContext.Post(d, arg);
56+
PostCore(d, arg, markCompleted: false);
5957
}
6058

6159
public void PostOperationCompleted(SendOrPostCallback d, object arg)
6260
{
63-
Post(d, arg);
61+
PostCore(d, arg, markCompleted: true);
6462
OperationCompletedCore();
6563
}
6664

6765
public void OperationCompleted()
6866
{
6967
VerifyNotCompleted();
68+
_alreadyCompleted = true;
7069
OperationCompletedCore();
7170
}
7271

72+
private void PostCore(SendOrPostCallback d, object arg, bool markCompleted)
73+
{
74+
VerifyNotCompleted();
75+
VerifyDelegateNotNull(d);
76+
if (markCompleted)
77+
{
78+
// This call is in response to a PostOperationCompleted. As such, we need to mark
79+
// _alreadyCompleted as true so that subsequent attempts to use this instance will
80+
// fail appropriately. And we need to do that before we queue the callback, as
81+
// that callback could itself trigger additional attempts to use this instance.
82+
_alreadyCompleted = true;
83+
}
84+
_syncContext.Post(d, arg);
85+
}
86+
7387
private void OperationCompletedCore()
7488
{
7589
try
@@ -78,7 +92,6 @@ private void OperationCompletedCore()
7892
}
7993
finally
8094
{
81-
_alreadyCompleted = true;
8295
GC.SuppressFinalize(this);
8396
}
8497
}

0 commit comments

Comments
 (0)