diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj
index 8cda15170f21ef..10eab660f6586e 100644
--- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj
+++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj
@@ -318,5 +318,6 @@
+
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
index 9934e5c97f6560..0df52bb456a2de 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs
@@ -710,7 +710,7 @@ private struct OperationQueue
// If we successfully process all enqueued operations, then the state becomes Ready;
// otherwise, the state becomes Waiting and we wait for another epoll notification.
- private enum QueueState : byte
+ private enum QueueState : int
{
Ready = 0, // Indicates that data MAY be available on the socket.
// Queue must be empty.
@@ -753,18 +753,31 @@ public void Init()
_sequenceNumber = 0;
}
- // IsReady returns the current _sequenceNumber, which must be passed to StartAsyncOperation below.
+ // IsReady returns whether an operation can be executed immediately.
+ // observedSequenceNumber must be passed to StartAsyncOperation.
public bool IsReady(SocketAsyncContext context, out int observedSequenceNumber)
{
- using (Lock())
- {
- observedSequenceNumber = _sequenceNumber;
- bool isReady = (_state == QueueState.Ready) || (_state == QueueState.Stopped);
+ // It is safe to read _state and _sequence without using Lock.
+ // - The return value is soley based on Volatile.Read of _state.
+ // - The Volatile.Read of _sequenceNumber ensures we read a value before executing the operation.
+ // This is needed to retry the operation in StartAsyncOperation in case the _sequenceNumber incremented.
+ // - Because no Lock is taken, it is possible we observe a sequence number increment before the state
+ // becomes Ready. When that happens, observedSequenceNumber is decremented, and StartAsyncOperation will
+ // execute the operation because the sequence number won't match.
- Trace(context, $"{isReady}");
+ Debug.Assert(sizeof(QueueState) == sizeof(int));
+ QueueState state = (QueueState)Volatile.Read(ref Unsafe.As(ref _state));
+ observedSequenceNumber = Volatile.Read(ref _sequenceNumber);
- return isReady;
+ bool isReady = state == QueueState.Ready || state == QueueState.Stopped;
+ if (!isReady)
+ {
+ observedSequenceNumber--;
}
+
+ Trace(context, $"{isReady}");
+
+ return isReady;
}
// Return true for pending, false for completed synchronously (including failure and abort)
@@ -1111,6 +1124,7 @@ public void CancelAndContinueProcessing(TOperation op)
if (_tail == null)
{
_state = QueueState.Ready;
+ _sequenceNumber++;
}
}
}