Skip to content

Commit 7345ac0

Browse files
committed
Back to synchronized version, since CAS approach demonstrates high contention
1 parent eb6f7a0 commit 7345ac0

File tree

4 files changed

+36
-49
lines changed

4 files changed

+36
-49
lines changed

src/DotNext.Threading/Threading/AsyncExchanger.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,20 @@ private ExchangePoint RentExchangePoint(T value)
8383

8484
private void OnCompleted(ExchangePoint point)
8585
{
86-
Monitor.Enter(SyncRoot);
87-
if (ReferenceEquals(this.point, point))
86+
lock (SyncRoot)
8887
{
89-
this.point = null;
88+
if (ReferenceEquals(this.point, point))
89+
{
90+
this.point = null;
91+
}
9092
}
9193

92-
Monitor.Exit(SyncRoot);
93-
9494
if (point.TryReset(out _))
9595
{
96-
pool.Return(point);
96+
lock (SyncRoot)
97+
{
98+
pool.Return(point);
99+
}
97100
}
98101
}
99102

src/DotNext.Threading/Threading/QueuedSynchronizer.Queue.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ private void ReturnNode(WaitNode node)
5151
// the node is removed for sure, it can be returned back to the pool
5252
if (node.TryReset(out _) && !IsDisposingOrDisposed)
5353
{
54-
pool.Return(node);
54+
lock (SyncRoot)
55+
{
56+
pool.Return(node);
57+
}
5558
}
5659
}
5760

Lines changed: 19 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Runtime.CompilerServices;
21
using System.Runtime.InteropServices;
32
using Debug = System.Diagnostics.Debug;
43

@@ -7,13 +6,13 @@ namespace DotNext.Threading.Tasks.Pooling;
76
/*
87
* Represents a pool without any allocations. Assuming that wait queues are organized using
98
* linked nodes where each node is a completion source. If so, the node pointers (next/previous)
10-
* can be used to keep completion sources in the pool. The implementation is thread-safe.
9+
* can be used to keep completion sources in the pool. The access must be synchronized.
1110
*/
1211
[StructLayout(LayoutKind.Auto)]
1312
internal struct ValueTaskPool<T>(long maximumRetained)
1413
{
15-
private volatile LinkedValueTaskCompletionSource<T>? first;
16-
private long count; // volatile
14+
private LinkedValueTaskCompletionSource<T>? first;
15+
private long count;
1716

1817
public ValueTaskPool()
1918
: this(long.MaxValue)
@@ -27,54 +26,33 @@ public void Return(LinkedValueTaskCompletionSource<T> node)
2726
Debug.Assert(node is { Status: ManualResetCompletionSourceStatus.WaitForActivation });
2827
Debug.Assert(node is { Next: null, Previous: null });
2928

30-
// try to increment the counter
31-
for (long current = Atomic.Read(in count), tmp; current < maximumRetained; current = tmp)
29+
if (count < maximumRetained)
3230
{
33-
tmp = Interlocked.CompareExchange(ref count, current + 1L, current);
34-
if (tmp == current)
35-
{
36-
ReturnCore(node);
37-
break;
38-
}
39-
}
40-
}
41-
42-
private void ReturnCore(LinkedValueTaskCompletionSource<T> node)
43-
{
44-
for (LinkedValueTaskCompletionSource<T>? current = first, tmp;; current = tmp)
45-
{
46-
node.Next = current;
47-
48-
tmp = Interlocked.CompareExchange(ref first, node, current);
49-
if (ReferenceEquals(tmp, current))
50-
break;
31+
node.Next = first;
32+
first = node;
33+
count++;
5134
}
5235
}
5336

5437
public TNode Rent<TNode>()
5538
where TNode : LinkedValueTaskCompletionSource<T>, new()
5639
{
57-
var current = first;
58-
for (LinkedValueTaskCompletionSource<T>? tmp;; current = Unsafe.As<TNode>(tmp))
40+
if (first is TNode result)
5941
{
60-
if (current is null)
61-
{
62-
current = new TNode();
63-
break;
64-
}
42+
first = result.Next;
43+
result.Next = null;
44+
count--;
6545

66-
tmp = Interlocked.CompareExchange(ref first, current.Next, current);
67-
if (!ReferenceEquals(tmp, current))
68-
continue;
46+
Debug.Assert(count >= 0L);
47+
Debug.Assert(count is 0L || first is not null);
48+
}
49+
else
50+
{
51+
Debug.Assert(count is 0L);
6952

70-
current.Next = null;
71-
var actualCount = Interlocked.Decrement(ref count);
72-
Debug.Assert(actualCount >= 0L);
73-
break;
53+
result = new();
7454
}
7555

76-
Debug.Assert(current is TNode);
77-
Debug.Assert(current is { Next: null, Previous: null });
78-
return Unsafe.As<TNode>(current);
56+
return result;
7957
}
8058
}

src/DotNext.Threading/Threading/Tasks/TaskCompletionPipe.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ private void OnCompleted(Signal signal)
5656

5757
if (signal.TryReset(out _))
5858
{
59-
pool.Return(signal);
59+
lock (SyncRoot)
60+
{
61+
pool.Return(signal);
62+
}
6063
}
6164
}
6265

0 commit comments

Comments
 (0)