Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -318,5 +318,6 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
<Reference Include="System.Threading.Thread" />
<Reference Include="System.Runtime.CompilerServices.Unsafe" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ private struct OperationQueue<TOperation>
// 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.
Expand Down Expand Up @@ -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<QueueState, int>(ref _state));
observedSequenceNumber = Volatile.Read(ref _sequenceNumber);

return isReady;
bool isReady = state == QueueState.Ready || state == QueueState.Stopped;
if (!isReady)
{
observedSequenceNumber--;
Copy link
Member

@stephentoub stephentoub Jun 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an enforced cap on the sequence number somewhere? I'm wondering what happens if, for example, this wraps around such that observedSequenceNumber is int.MinValue and observedSequenceNumber - 1 is int.MaxValue.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no cap on sequence number. The observed sequence number gets checked for equality in StartAsyncOperation. It is a wrapping counter for the nr of times the queue becomes ready. Decrementing it is the same as pretending we have 'observed' the previous time the queue became ready, so StartAsyncOperation considers IsReady state as a new thing.
I think there is nothing special at int.{Min,Max}Value because we're doing ++, -- and != operations only.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I wasn't sure if it was ever compared with <. If it's only ever compared with == seems fine. Out of curiosity, what would happen if 4B operations occurred between the time we read the value and compare it such that it fully wrapped and was again the same? I assume if that's a (super rare) problem, it's already a problem.

Copy link
Member Author

@tmds tmds Jun 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, what would happen if 4B operations occurred between the time we read the value and compare it such that it fully wrapped and was again the same?

We'd not be aware events happened, and the socket operation will never complete. Since there is a limited nr of cases that cause increments, we don't need to worry about reaching 4B increments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, not actually concerned, but trying to understand... re "Since there is a limited nr of cases that cause increments, we don't need to worry about reaching 4B increments", what would prevent one thread from stalling in between the get and the check, and other threads processing 4B operations in between?

}

Trace(context, $"{isReady}");

return isReady;
}

// Return true for pending, false for completed synchronously (including failure and abort)
Expand Down Expand Up @@ -1111,6 +1124,7 @@ public void CancelAndContinueProcessing(TOperation op)
if (_tail == null)
{
_state = QueueState.Ready;
_sequenceNumber++;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is unrelated. All code paths that set _state to QueueState.Ready increment _sequenceNumber. I'm not sure why it is not done at this location, so I've added it.

}
}
}
Expand Down