On Windows, it is possible to use the following WIN API function to enable WSARecv and other calls on the socket to complete synchronously when possible and avoid triggering IOCP completion: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365538(v=vs.85).aspx
It is possible to do even now, e.g.:
``` C#
public static bool SetFileCompletionNotificationModes(this Socket socket, FileCompletionNotificationModes flags)
{
SetFileCompletionNotificationModes(socket.Handle, flags);
}
[DllImport("kernel32.dll")]
internal static extern bool SetFileCompletionNotificationModes([In] IntPtr fileHandle, [In] FileCompletionNotificationModes flags);
[Flags]
public enum FileCompletionNotificationModes
{
SkipCompletionPortOnSuccess = 1,
SkipSetEventOnHandle = 2
}
The problem is in ReceiveAsync implementation on this line: https://github.com/dotnet/corefx/blob/5e89fcf41481481e7b673b10ea7644ce56776bd9/src/System.Net.Sockets/src/System/Net/Sockets/Socket.cs#L4356:
``` C#
if (socketError != SocketError.Success && socketError != SocketError.IOPending)
{
e.FinishOperationSyncFailure(socketError, bytesTransferred, flags);
retval = false;
}
else
{
retval = true;
}
Basically, if returned result is not Success or IOPending then it is a sync completion with error. If it is Success or IOPending - it will return true.
WSARecv will not return SUCCESS for IOCP-based call unless SetFileCompletionNotificationModes was called for the socket setting the flags above. So in default operation mode check for inequality to SocketError.Success is harmless (but redundant) and is always true. Once you set notification modes though, WSARecv may return SUCCESS in case of sync completion and then it won't invoke callback on SAEA and per code above will return true.
Proposal is to change behavior of ReceiveAsync and other IOCP-based methods to support synchronous completion. The code above would change as follows:
C#
if (socketError == SocketError.Success)
{
e.FinishOperationSuccess(SocketError.Success, bytesTransferred, flags, completedSynchronously: true);
retval = false;
}
else
{
socketError = (SocketError)Marshal.GetLastWin32Error();
if (socketError == SocketError.IOPending)
{
retval = true;
}
else
{
e.FinishOperationSyncFailure(socketError, bytesTransferred, SocketFlags.None);
retval = false;
}
}
Another change not shown here is to SocketAsyncEventArgs.FinishOperationSuccess, adding parameter bool completedSynchronously that if true would skip raising SAEA.Completed event.
This change and support for SetFileCompletionNotificationModes yield significant performance improvement for scenarios with high throughput over a small number of sockets.
Let me know if the change is something you can consider to take and I'd post a PR.
I think this is a dup of https://github.com/dotnet/corefx/issues/4900.
I would very much like to see this get fixed.
cc: @davidsh, @cipop, @geoffkizer
@nayato, @davidsh / @cipop can comment as well, but personally I think it'd be great if you could put together a PR to address this. Note that it needs to include handling the Unix side as well; while Unix doesn't have this distinction, our Unix implementation explicitly matches the current Windows behavior, e.g. https://github.com/dotnet/corefx/blob/master/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs#L886-L893, so that will need to be addressed as well.
@stephentoub, also I don't think it will be OK to enable the flag on socket by default as I guess there is a lot of code out there relying on current behavior. How would you want to expose it? a property bool AllowSyncCompletionOnAsyncOperations on Socket?
For Unix implementation, in place you've mentioned I suppose if sync completion is allowed, it would just return Success otherwise, do what it does + return IOPending.
FYI, I'm also checking with TCP guys in Windows as to what happened with this bug: https://github.com/libuv/libuv/blob/v1.x/src/win/udp.c#L87
I know it's been fixed in Win 8 but I'm not sure about earlier versions.
also I don't think it will be OK to enable the flag on socket by default as I guess there is a lot of code out there relying on current behavior. How would you want to expose it?
@cipop / @davidsh should comment, but I actually think we should fix it in the current implementation. I'm interested in other opinions, too, though... @jkotas? @geoffkizer? @pgavlin? My view is that it's simply a bug in the implementation today that operations complete asynchronously when they could complete synchronously. The docs for SocketAsyncEventArgs are already very clear that operations can complete synchronously, with the API explicitly designed for that, so it's effectively a bug in someone's code if they're not already planning for that possibility.
Confirmed that the bug fix mentioned above was not backported to Win 7 and corresponding Win Server. So it is dangerous to just enable this behavior on Windows by default (at least for UDP sockets) as Win 7 SP1 is a supported platforms.
Confirmed that the bug fix mentioned above was not backported to Win 7 and corresponding Win Server. So it is dangerous to just enable this behavior on Windows by default (at least for UDP sockets) as Win 7 SP1 is a supported platforms.
That just suggests it couldn't be enabled by default pre-Win8.
I still think it might be a good idea to expose it as a property. It is similar to how Blocking property works:
Effect is more subtle though.
I agree with @stephentoub. This should be enabled by default.
@nayato please include me too in the discussion with WinSock.
@CIPop OK. How should I check whether it's safe to set the notification modes? Check Environment.OSVersion if protocol is UDP?
@nayato, the ideal way to do this is without checking for the OS version. Is there any way we can avoid platform detection and infer the availability from Winsock APIs?
@CIPop the problem is that on OS prior to Win 8 there is a bug with SkipCompletionPortOnSuccess on UDP socket. As .NET Core supports Win 7 SP1 I don't know how to get around it without version check. Unless you want to skip enabling it on any UDP socket regardless of OS version.
@nayato I understand that: was hoping there's some cool hack to determine if the code has the bug or not. (e.g. maybe in Win8 when the bug was fixed, the winsock DLL has a few more APIs: if those are detected we are 100% sure the bug is also fixed.) I think it's worth asking the WinSock team internally before adding the platform version check.
In the past, @weshaggard and @ericstj proposed that we avoid querying the OS version as much as possible.
In the past, @weshaggard and @ericstj proposed that we avoid querying the OS version as much as possible.
Querying for the OS isn't inherently bad we just need to ensure we do the querying for OS and Version in such a way that we continue to work in the future compatible version of the OS. The general problem that people don't correctly future proof and do things like check for exact versions and other things that end up causing the code to break in the future. Which is why we generally suggest trying to query for the specific feature you are looking for instead of the OS itself.
Anyone know more details of the Windows bug? What exactly happens if you try to use this with UDP on a pre-Win8?
This is a duplicate of dotnet/runtime#15895. Normally I'd keep this issue active since it has relevant new information but 4900 is referenced in code as well and so cannot be closed.
I'll try to add a summary in the original bug.
Most helpful comment
@cipop / @davidsh should comment, but I actually think we should fix it in the current implementation. I'm interested in other opinions, too, though... @jkotas? @geoffkizer? @pgavlin? My view is that it's simply a bug in the implementation today that operations complete asynchronously when they could complete synchronously. The docs for SocketAsyncEventArgs are already very clear that operations can complete synchronously, with the API explicitly designed for that, so it's effectively a bug in someone's code if they're not already planning for that possibility.