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++; } } }