Skip to content

Commit 9571a02

Browse files
committed
Removed contention during the activation
1 parent c6a39a5 commit 9571a02

File tree

1 file changed

+21
-11
lines changed

1 file changed

+21
-11
lines changed

src/DotNext.Threading/Threading/Tasks/ManualResetCompletionSource.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.ComponentModel;
22
using System.Diagnostics;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Numerics;
45
using System.Runtime.CompilerServices;
56
using System.Runtime.InteropServices;
67
using System.Threading.Tasks.Sources;
@@ -92,7 +93,7 @@ private CancellationState ResetCore(out short token)
9293
/// Resets the state of the source.
9394
/// </summary>
9495
/// <remarks>
95-
/// This methods acts as a barrier for completion.
96+
/// This method acts as a barrier for completion.
9697
/// It means that calling of this method guarantees that the task
9798
/// cannot be completed by the previously linked timeout or cancellation token.
9899
/// </remarks>
@@ -293,10 +294,16 @@ public void OnCompleted(Action<object?> continuation, object? state, short token
293294
{
294295
Timeout.Validate(timeout);
295296

296-
// The task can be created for the completed (but not yet consumed) source.
297-
// This workaround is needed for AsyncBridge methods
297+
// The source can be completed before the activation. Moreover, someone could try
298+
// to complete the task during the activation concurrently. To avoid lock contention,
299+
// use TryEnterLock(). If we can't acquire the lock, there is concurrent completion.
300+
// In that case, do not activate the source and skip fast.
301+
return TryEnterLock() ? ActivateSlow(timeout, token) : versionAndStatus.Version;
302+
}
303+
304+
private short? ActivateSlow(TimeSpan timeout, CancellationToken token)
305+
{
298306
short? result;
299-
EnterLock();
300307
try
301308
{
302309
switch (versionAndStatus.Status)
@@ -305,11 +312,9 @@ public void OnCompleted(Action<object?> continuation, object? state, short token
305312
CompleteAsTimedOut();
306313
goto case ManualResetCompletionSourceStatus.WaitForConsumption;
307314
case ManualResetCompletionSourceStatus.WaitForActivation:
308-
state.Initialize(ref versionAndStatus, cancellationCallback, timeout, token);
309-
310-
if (token.IsCancellationRequested)
315+
if (!state.Initialize(ref versionAndStatus, cancellationCallback, timeout, token))
311316
CompleteAsCanceled(token);
312-
317+
313318
goto case ManualResetCompletionSourceStatus.WaitForConsumption;
314319
case ManualResetCompletionSourceStatus.WaitForConsumption:
315320
result = versionAndStatus.Version;
@@ -570,10 +575,10 @@ private struct CancellationState : IDisposable
570575
private CancellationTokenRegistration tokenTracker;
571576
private CancellationTokenSource? timeoutSource;
572577

573-
internal void Initialize(ref VersionAndStatus vs, Action<object?, CancellationToken> callback, TimeSpan timeout, CancellationToken token)
578+
internal bool Initialize(ref VersionAndStatus vs, Action<object?, CancellationToken> callback, TimeSpan timeout, CancellationToken token)
574579
{
575580
// box current token once and only if needed
576-
var cachedVersion = default(IEquatable<short>);
581+
IBinaryInteger<short>? cachedVersion = null;
577582

578583
if (token.CanBeCanceled)
579584
{
@@ -586,7 +591,10 @@ internal void Initialize(ref VersionAndStatus vs, Action<object?, CancellationTo
586591
// To avoid that, change the status later and check the token after calling this method
587592
vs.Status = ManualResetCompletionSourceStatus.Activated;
588593

589-
if (timeout > default(TimeSpan) && !token.IsCancellationRequested)
594+
if (token.IsCancellationRequested)
595+
return false;
596+
597+
if (timeout > default(TimeSpan))
590598
{
591599
timeoutSource ??= new();
592600

@@ -595,6 +603,8 @@ internal void Initialize(ref VersionAndStatus vs, Action<object?, CancellationTo
595603
timeoutSource.Token.UnsafeRegister(callback, cachedVersion ?? vs.Version);
596604
timeoutSource.CancelAfter(timeout);
597605
}
606+
607+
return true;
598608
}
599609

600610
internal readonly bool IsTimeoutToken(CancellationToken token)

0 commit comments

Comments
 (0)