Skip to content

Commit 3033fe7

Browse files
committed
Move to normal monitor lock due to expensive contention
1 parent c9ca86b commit 3033fe7

File tree

4 files changed

+26
-85
lines changed

4 files changed

+26
-85
lines changed

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

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,8 @@ namespace DotNext.Threading.Tasks;
55

66
public partial class ManualResetCompletionSource
77
{
8-
private int lockState;
9-
10-
// a chance of lock contention for this instance is very low
11-
// so monitor lock is too heavyweight for synchronization purposes
12-
private protected void EnterLock()
13-
{
14-
ref var lockState = ref this.lockState;
15-
if (Interlocked.Exchange(ref lockState, 1) is 1)
16-
Contention(ref lockState);
17-
18-
[MethodImpl(MethodImplOptions.NoInlining)]
19-
static void Contention(ref int lockState)
20-
{
21-
var spinner = new SpinWait();
22-
do
23-
{
24-
spinner.SpinOnce();
25-
}
26-
while (Interlocked.Exchange(ref lockState, 1) is 1);
27-
}
28-
}
29-
30-
private bool TryEnterLock() => Interlocked.Exchange(ref lockState, 1) is 0;
31-
32-
private protected void ExitLock()
33-
{
34-
AssertLocked();
35-
36-
Volatile.Write(ref lockState, 0);
37-
}
8+
private protected object SyncRoot => cancellationCallback;
389

3910
[Conditional("DEBUG")]
40-
private protected void AssertLocked() => Debug.Assert(lockState is not 0);
11+
private protected void AssertLocked() => Debug.Assert(Monitor.IsEntered(SyncRoot));
4112
}

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

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ private void CancellationRequested(object? expectedVersion, CancellationToken to
4747
if (versionAndStatus.Status is not ManualResetCompletionSourceStatus.Activated)
4848
goto exit;
4949

50-
EnterLock();
51-
try
50+
lock (SyncRoot)
5251
{
5352
if (versionAndStatus.Status is not ManualResetCompletionSourceStatus.Activated
5453
|| versionAndStatus.Version != (short)expectedVersion
@@ -58,14 +57,10 @@ private void CancellationRequested(object? expectedVersion, CancellationToken to
5857
// ensure that timeout or cancellation handler sets the status correctly
5958
Debug.Assert(versionAndStatus.Status is ManualResetCompletionSourceStatus.WaitForConsumption);
6059
}
61-
finally
62-
{
63-
ExitLock();
64-
}
6560

6661
Resume();
6762

68-
exit:
63+
exit:
6964
return;
7065
}
7166

@@ -100,9 +95,9 @@ private CancellationState ResetCore(out short token)
10095
/// <returns>The version of the uncompleted task.</returns>
10196
public short Reset()
10297
{
103-
EnterLock();
98+
Monitor.Enter(SyncRoot);
10499
var stateCopy = ResetCore(out var token);
105-
ExitLock();
100+
Monitor.Exit(SyncRoot);
106101

107102
stateCopy.Dispose();
108103
CleanUp();
@@ -119,10 +114,10 @@ public bool TryReset(out short token)
119114
{
120115
bool result;
121116

122-
if (result = TryEnterLock())
117+
if (result = Monitor.TryEnter(SyncRoot))
123118
{
124119
var stateCopy = ResetCore(out token);
125-
ExitLock();
120+
Monitor.Exit(SyncRoot);
126121

127122
stateCopy.Dispose();
128123
CleanUp();
@@ -190,26 +185,26 @@ private void OnCompleted(in Continuation continuation, short token)
190185

191186
// code block doesn't have any calls leading to exceptions
192187
// so replace try-finally with manually cloned code
193-
EnterLock();
188+
Monitor.Enter(SyncRoot);
194189
if (token != versionAndStatus.Version)
195190
{
196191
errorMessage = ExceptionMessages.InvalidSourceToken;
197-
ExitLock();
192+
Monitor.Exit(SyncRoot);
198193
goto invalid_state;
199194
}
200195

201196
switch (versionAndStatus.Status)
202197
{
203198
default:
204199
errorMessage = ExceptionMessages.InvalidSourceState;
205-
ExitLock();
200+
Monitor.Exit(SyncRoot);
206201
goto invalid_state;
207202
case ManualResetCompletionSourceStatus.WaitForConsumption:
208-
ExitLock();
203+
Monitor.Exit(SyncRoot);
209204
break;
210205
case ManualResetCompletionSourceStatus.Activated:
211206
this.continuation = continuation;
212-
ExitLock();
207+
Monitor.Exit(SyncRoot);
213208
goto exit;
214209
}
215210

@@ -294,27 +289,21 @@ public void OnCompleted(Action<object?> continuation, object? state, short token
294289
{
295290
Timeout.Validate(timeout);
296291

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-
{
306292
short? result;
307-
try
293+
lock (SyncRoot)
308294
{
309295
switch (versionAndStatus.Status)
310296
{
311-
case ManualResetCompletionSourceStatus.WaitForActivation when timeout == default:
312-
CompleteAsTimedOut();
313-
goto case ManualResetCompletionSourceStatus.WaitForConsumption;
314297
case ManualResetCompletionSourceStatus.WaitForActivation:
315-
if (!state.Initialize(ref versionAndStatus, cancellationCallback, timeout, token))
298+
if (timeout == TimeSpan.Zero)
299+
{
300+
CompleteAsTimedOut();
301+
}
302+
else if (!state.Initialize(ref versionAndStatus, cancellationCallback, timeout, token))
303+
{
316304
CompleteAsCanceled(token);
317-
305+
}
306+
318307
goto case ManualResetCompletionSourceStatus.WaitForConsumption;
319308
case ManualResetCompletionSourceStatus.WaitForConsumption:
320309
result = versionAndStatus.Version;
@@ -324,10 +313,6 @@ public void OnCompleted(Action<object?> continuation, object? state, short token
324313
break;
325314
}
326315
}
327-
finally
328-
{
329-
ExitLock();
330-
}
331316

332317
return result;
333318
}

src/DotNext.Threading/Threading/Tasks/ValueTaskCompletionSource.T.cs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,21 +134,16 @@ private unsafe bool SetResult<TArg>(object? completionData, short? completionTok
134134
Debug.Assert(func != null);
135135

136136
bool result;
137-
EnterLock();
138-
try
137+
lock (SyncRoot)
139138
{
140139
result = versionAndStatus.CanBeCompleted(completionToken);
141140
if (!result || !SetResult(func(arg), completionData))
142141
goto exit;
143142
}
144-
finally
145-
{
146-
ExitLock();
147-
}
148143

149144
Resume();
150145

151-
exit:
146+
exit:
152147
return result;
153148
}
154149

@@ -163,15 +158,10 @@ private bool SetResult(in Result<T> result, object? completionData = null)
163158
internal bool TrySetResult(object? completionData, short? completionToken, in Result<T> result, out bool resumable)
164159
{
165160
bool successful;
166-
EnterLock();
167-
try
161+
lock (SyncRoot)
168162
{
169163
resumable = (successful = versionAndStatus.CanBeCompleted(completionToken)) && SetResult(in result, completionData);
170164
}
171-
finally
172-
{
173-
ExitLock();
174-
}
175165

176166
return successful;
177167
}

src/DotNext.Threading/Threading/Tasks/ValueTaskCompletionSource.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,13 @@ private bool SetResult<TFactory>(object? completionData, short? completionToken,
6868
where TFactory : ISupplier<Exception?>
6969
{
7070
bool result;
71-
EnterLock();
72-
try
71+
lock (SyncRoot)
7372
{
7473
result = versionAndStatus.CanBeCompleted(completionToken);
7574

7675
if (!result || !SetResult(factory.Invoke(), completionData))
7776
goto exit;
7877
}
79-
finally
80-
{
81-
ExitLock();
82-
}
8378

8479
Resume();
8580

0 commit comments

Comments
 (0)