Skip to content

Commit dbb82b9

Browse files
committed
Avoid an Interlocked.Exchange in the common case
1 parent 7e6dd68 commit dbb82b9

File tree

1 file changed

+22
-12
lines changed

1 file changed

+22
-12
lines changed

src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ private unsafe SocketError ProcessIOCPResult(bool success, int bytesTransferred,
219219
}
220220

221221
// We've finished setting up and launching the operation. Coordinate with the callback.
222+
// The expectation is that in the majority of cases either the operation will have completed
223+
// synchronously (in which case we won't be here) or the operation will complete asynchronously
224+
// and this function will typically win the race condition with the callback.
222225
ulong packedResult = Interlocked.Exchange(ref _asyncCompletionOwnership, 1);
223226
if (packedResult == 0)
224227
{
@@ -1223,19 +1226,26 @@ private void FinishOperationSendPackets()
12231226
Debug.Assert(saeaBox.Value != null);
12241227
SocketAsyncEventArgs saea = saeaBox.Value;
12251228

1226-
// Pack the error code and number of bytes transferred into a single ulong we can store into
1227-
// _asyncCompletionOwnership. If the field was already set by the launcher, the value won't
1228-
// be needed, but if this callback wins the race condition and transfers ownership to the
1229-
// launcher to handle completion and clean up, transfering these values over prevents needing
1230-
// to make an additional call to WSAGetOverlappedResult.
1231-
Debug.Assert(numBytes <= int.MaxValue, "We rely on being able to set the top bit to ensure the whole packed result isn't 0.");
1232-
ulong packedResult = (1ul << 63) | ((ulong)numBytes << 32) | errorCode;
1233-
1234-
if (Interlocked.Exchange(ref saea._asyncCompletionOwnership, packedResult) == 0)
1229+
// We need to coordinate with the launching thread, just in case it hasn't yet finished setting up the operation.
1230+
// We typically expect the launching thread to have already completed setup, in which case _asyncCompletionOwnership
1231+
// will be 1, so we do a fast non-synchronized check to see if it's still 0, and only if it is do we proceed to
1232+
// pack the results for use with an interlocked coordination with that thread.
1233+
if (saea._asyncCompletionOwnership == 0)
12351234
{
1236-
// The operation completed asynchronously so quickly that the thread launching the operation still hasn't finished setting
1237-
// up the state for the operation. Leave all cleanup and completion logic to that thread.
1238-
return;
1235+
// Pack the error code and number of bytes transferred into a single ulong we can store into
1236+
// _asyncCompletionOwnership. If the field was already set by the launcher, the value won't
1237+
// be needed, but if this callback wins the race condition and transfers ownership to the
1238+
// launcher to handle completion and clean up, transfering these values over prevents needing
1239+
// to make an additional call to WSAGetOverlappedResult.
1240+
Debug.Assert(numBytes <= int.MaxValue, "We rely on being able to set the top bit to ensure the whole packed result isn't 0.");
1241+
ulong packedResult = (1ul << 63) | ((ulong)numBytes << 32) | errorCode;
1242+
1243+
if (Interlocked.Exchange(ref saea._asyncCompletionOwnership, packedResult) == 0)
1244+
{
1245+
// The operation completed asynchronously so quickly that the thread launching the operation still hasn't finished setting
1246+
// up the state for the operation. Leave all cleanup and completion logic to that thread.
1247+
return;
1248+
}
12391249
}
12401250

12411251
// This callback owns the completion and cleanup for the operation.

0 commit comments

Comments
 (0)