From e257962fc1cb447b9aa7d5c188f5eed4a60ce619 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 13 May 2020 22:39:57 +0200 Subject: [PATCH 1/7] Try using socket syscalls that accepts a single buffer to improve performance --- .../Unix/System.Native/Interop.Receive.cs | 16 +++ .../Unix/System.Native/Interop.Send.cs | 16 +++ .../Unix/System.Native/pal_networking.c | 60 ++++++++++ .../Unix/System.Native/pal_networking.h | 4 + .../src/System.Net.Sockets.csproj | 4 + .../Net/Sockets/SocketAsyncContext.Unix.cs | 44 ++++++- .../Net/Sockets/SocketAsyncEventArgs.Unix.cs | 10 +- .../src/System/Net/Sockets/SocketPal.Unix.cs | 107 +++++++++++++++++- 8 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.Receive.cs create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.Send.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Receive.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Receive.cs new file mode 100644 index 00000000000000..8f3cdbf9db07a8 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Receive.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Receive")] + internal static extern unsafe Error Receive(SafeHandle socket, byte* buffer, int bufferLen, SocketFlags flags, int* received); + } +} diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Send.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Send.cs new file mode 100644 index 00000000000000..b4a7e1c9bc8320 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Send.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Send")] + internal static extern unsafe Error Send(SafeHandle socket, byte* buffer, int bufferLen, SocketFlags flags, int* sent); + } +} diff --git a/src/libraries/Native/Unix/System.Native/pal_networking.c b/src/libraries/Native/Unix/System.Native/pal_networking.c index 47d4273f6dc505..305725a09797fc 100644 --- a/src/libraries/Native/Unix/System.Native/pal_networking.c +++ b/src/libraries/Native/Unix/System.Native/pal_networking.c @@ -1347,6 +1347,34 @@ static int32_t ConvertSocketFlagsPlatformToPal(int platformFlags) ((platformFlags & MSG_CTRUNC) == 0 ? 0 : SocketFlags_MSG_CTRUNC); } +int32_t SystemNative_Receive(intptr_t socket, void* buffer, int32_t bufferLen, int32_t flags, int32_t* received) +{ + if (buffer == NULL || bufferLen < 0 || received == NULL) + { + return Error_EFAULT; + } + + int fd = ToFileDescriptor(socket); + + int socketFlags; + if (!ConvertSocketFlagsPalToPlatform(flags, &socketFlags)) + { + return Error_ENOTSUP; + } + + ssize_t res; + while ((res = recv(fd, buffer, (size_t)bufferLen, socketFlags)) < 0 && errno == EINTR); + + if (res != -1) + { + *received = (int32_t)res; + return Error_SUCCESS; + } + + *received = 0; + return SystemNative_ConvertErrorPlatformToPal(errno); +} + int32_t SystemNative_ReceiveMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* received) { if (messageHeader == NULL || received == NULL || messageHeader->SocketAddressLen < 0 || @@ -1390,6 +1418,38 @@ int32_t SystemNative_ReceiveMessage(intptr_t socket, MessageHeader* messageHeade return SystemNative_ConvertErrorPlatformToPal(errno); } +int32_t SystemNative_Send(intptr_t socket, void* buffer, int32_t bufferLen, int32_t flags, int32_t* sent) +{ + if (buffer == NULL || bufferLen < 0 || sent == NULL) + { + return Error_EFAULT; + } + + int fd = ToFileDescriptor(socket); + + int socketFlags; + if (!ConvertSocketFlagsPalToPlatform(flags, &socketFlags)) + { + return Error_ENOTSUP; + } + + ssize_t res; +#if defined(__APPLE__) && __APPLE__ + // possible OSX kernel bug: #31927 + while ((res = send(fd, buffer, (size_t)bufferLen, socketFlags)) < 0 && (errno == EINTR || errno == EPROTOTYPE)); +#else + while ((res = send(fd, buffer, (size_t)bufferLen, socketFlags)) < 0 && errno == EINTR); +#endif + if (res != -1) + { + *sent = (int32_t)res; + return Error_SUCCESS; + } + + *sent = 0; + return SystemNative_ConvertErrorPlatformToPal(errno); +} + int32_t SystemNative_SendMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* sent) { if (messageHeader == NULL || sent == NULL || messageHeader->SocketAddressLen < 0 || diff --git a/src/libraries/Native/Unix/System.Native/pal_networking.h b/src/libraries/Native/Unix/System.Native/pal_networking.h index 8d228c9f1bac40..e24b8614a1cf0d 100644 --- a/src/libraries/Native/Unix/System.Native/pal_networking.h +++ b/src/libraries/Native/Unix/System.Native/pal_networking.h @@ -371,8 +371,12 @@ PALEXPORT int32_t SystemNative_SetReceiveTimeout(intptr_t socket, int32_t millis PALEXPORT int32_t SystemNative_SetSendTimeout(intptr_t socket, int32_t millisecondsTimeout); +PALEXPORT int32_t SystemNative_Receive(intptr_t socket, void* buffer, int32_t bufferLen, int32_t flags, int32_t* received); + PALEXPORT int32_t SystemNative_ReceiveMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* received); +PALEXPORT int32_t SystemNative_Send(intptr_t socket, void* buffer, int32_t bufferLen, int32_t flags, int32_t* sent); + PALEXPORT int32_t SystemNative_SendMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* sent); PALEXPORT int32_t SystemNative_Accept(intptr_t socket, uint8_t* socketAddress, int32_t* socketAddressLen, intptr_t* acceptedSocket); 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 f8a2cce64caf21..98b9d78f9f70fc 100644 --- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -274,8 +274,12 @@ Link="Common\Interop\Unix\System.Native\Interop.Poll.cs" /> + + private sealed class BufferMemoryReceiveOperation : ReceiveOperation { public Memory Buffer; + public bool SetReceivedFlags; public BufferMemoryReceiveOperation(SocketAsyncContext context) : base(context) { } @@ -479,7 +480,14 @@ protected override bool DoTryComplete(SocketAsyncContext context) } else { - return SocketPal.TryCompleteReceiveFrom(context._socket, Buffer.Span, null, Flags, SocketAddress, ref SocketAddressLen, out BytesTransferred, out ReceivedFlags, out ErrorCode); + if (!SetReceivedFlags && SocketAddress == null) + { + return SocketPal.TryCompleteReceive(context._socket, Buffer.Span, Flags, out BytesTransferred, out ErrorCode); + } + else + { + return SocketPal.TryCompleteReceiveFrom(context._socket, Buffer.Span, null, Flags, SocketAddress, ref SocketAddressLen, out BytesTransferred, out ReceivedFlags, out ErrorCode); + } } } @@ -1545,6 +1553,39 @@ public unsafe SocketError ReceiveFrom(Span buffer, ref SocketFlags flags, } } + public SocketError ReceiveAsync(Memory buffer, SocketFlags flags, out int bytesReceived, Action callback, CancellationToken cancellationToken = default) + { + SetNonBlocking(); + + SocketError errorCode; + int observedSequenceNumber; + if (_receiveQueue.IsReady(this, out observedSequenceNumber) && + SocketPal.TryCompleteReceive(_socket, buffer.Span, flags, out bytesReceived, out errorCode)) + { + return errorCode; + } + + BufferMemoryReceiveOperation operation = RentBufferMemoryReceiveOperation(); + operation.SetReceivedFlags = false; + operation.Callback = callback; + operation.Buffer = buffer; + operation.Flags = flags; + operation.SocketAddress = null; + operation.SocketAddressLen = 0; + + if (!_receiveQueue.StartAsyncOperation(this, operation, observedSequenceNumber, cancellationToken)) + { + bytesReceived = operation.BytesTransferred; + errorCode = operation.ErrorCode; + + ReturnOperation(operation); + return errorCode; + } + + bytesReceived = 0; + return SocketError.IOPending; + } + public SocketError ReceiveFromAsync(Memory buffer, SocketFlags flags, byte[]? socketAddress, ref int socketAddressLen, out int bytesReceived, out SocketFlags receivedFlags, Action callback, CancellationToken cancellationToken = default) { SetNonBlocking(); @@ -1558,6 +1599,7 @@ public SocketError ReceiveFromAsync(Memory buffer, SocketFlags flags, byt } BufferMemoryReceiveOperation operation = RentBufferMemoryReceiveOperation(); + operation.SetReceivedFlags = true; operation.Callback = callback; operation.Buffer = buffer; operation.Flags = flags; diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs index d2ca491a52ae9a..5083c56bcb2030 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs @@ -128,7 +128,15 @@ internal unsafe SocketError DoOperationReceive(SafeSocketHandle handle, Cancella SocketError errorCode; if (_bufferList == null) { - errorCode = handle.AsyncContext.ReceiveAsync(_buffer.Slice(_offset, _count), _socketFlags, out bytesReceived, out flags, TransferCompletionCallback, cancellationToken); + if (_currentSocket!.ProtocolType == ProtocolType.Tcp) // no out-going socket flags. + { + flags = SocketFlags.None; + errorCode = handle.AsyncContext.ReceiveAsync(_buffer.Slice(_offset, _count), _socketFlags, out bytesReceived, TransferCompletionCallback, cancellationToken); + } + else + { + errorCode = handle.AsyncContext.ReceiveAsync(_buffer.Slice(_offset, _count), _socketFlags, out bytesReceived, out flags, TransferCompletionCallback, cancellationToken); + } } else { diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs index 68f8c82df2956e..43cb15667047bf 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs @@ -96,6 +96,28 @@ public static unsafe SocketError CreateSocket(AddressFamily addressFamily, Socke return errorCode; } + private static unsafe int Receive(SafeSocketHandle socket, SocketFlags flags, Span buffer, out Interop.Error errno) + { + int received = 0; + + fixed (byte* b = &MemoryMarshal.GetReference(buffer)) + { + errno = Interop.Sys.Receive( + socket, + b, + buffer.Length, + flags, + &received); + } + + if (errno != Interop.Error.SUCCESS) + { + return -1; + } + + return received; + } + private static unsafe int Receive(SafeSocketHandle socket, SocketFlags flags, Span buffer, byte[]? socketAddress, ref int socketAddressLen, out SocketFlags receivedFlags, out Interop.Error errno) { Debug.Assert(socketAddress != null || socketAddressLen == 0, $"Unexpected values: socketAddress={socketAddress}, socketAddressLen={socketAddressLen}"); @@ -137,7 +159,33 @@ private static unsafe int Receive(SafeSocketHandle socket, SocketFlags flags, Sp return checked((int)received); } - private static unsafe int Send(SafeSocketHandle socket, SocketFlags flags, ReadOnlySpan buffer, ref int offset, ref int count, byte[]? socketAddress, int socketAddressLen, out Interop.Error errno) + private static unsafe int Send(SafeSocketHandle socket, SocketFlags flags, ReadOnlySpan buffer, ref int offset, ref int count, out Interop.Error errno) + { + int sent; + fixed (byte* b = &MemoryMarshal.GetReference(buffer)) + { + int bytesSent = 0; + errno = Interop.Sys.Send( + socket, + &b[offset], + count, + flags, + &bytesSent); + + sent = bytesSent; + } + + if (errno != Interop.Error.SUCCESS) + { + return -1; + } + + offset += sent; + count -= sent; + return sent; + } + + private static unsafe int Send(SafeSocketHandle socket, SocketFlags flags, ReadOnlySpan buffer, ref int offset, ref int count, byte[] socketAddress, int socketAddressLen, out Interop.Error errno) { int sent; fixed (byte* sockAddr = socketAddress) @@ -617,6 +665,60 @@ public static bool TryCompleteReceiveFrom(SafeSocketHandle socket, Span bu public static bool TryCompleteReceiveFrom(SafeSocketHandle socket, IList> buffers, SocketFlags flags, byte[]? socketAddress, ref int socketAddressLen, out int bytesReceived, out SocketFlags receivedFlags, out SocketError errorCode) => TryCompleteReceiveFrom(socket, default(Span), buffers, flags, socketAddress, ref socketAddressLen, out bytesReceived, out receivedFlags, out errorCode); + public static unsafe bool TryCompleteReceive(SafeSocketHandle socket, Span buffer, SocketFlags flags, out int bytesReceived, out SocketError errorCode) + { + try + { + Interop.Error errno; + int received; + + if (buffer.Length == 0) + { + // Special case a receive of 0 bytes into a single buffer. A common pattern is to ReceiveAsync 0 bytes in order + // to be asynchronously notified when data is available, without needing to dedicate a buffer. Some platforms (e.g. macOS), + // however complete a 0-byte read successfully when data isn't available, as the request can logically be satisfied + // synchronously. As such, we treat 0 specially, and perform a 1-byte peek. + byte oneBytePeekBuffer; + received = Receive(socket, flags | SocketFlags.Peek, new Span(&oneBytePeekBuffer, 1), out errno); + if (received > 0) + { + // Peeked for 1-byte, but the actual request was for 0. + received = 0; + } + } + else + { + // Receive > 0 bytes into a single buffer + received = Receive(socket, flags, buffer, out errno); + } + + if (received != -1) + { + bytesReceived = received; + errorCode = SocketError.Success; + return true; + } + + bytesReceived = 0; + + if (errno != Interop.Error.EAGAIN && errno != Interop.Error.EWOULDBLOCK) + { + errorCode = GetSocketErrorForErrorCode(errno); + return true; + } + + errorCode = SocketError.Success; + return false; + } + catch (ObjectDisposedException) + { + // The socket was closed, or is closing. + bytesReceived = 0; + errorCode = SocketError.OperationAborted; + return true; + } + } + public static unsafe bool TryCompleteReceiveFrom(SafeSocketHandle socket, Span buffer, IList>? buffers, SocketFlags flags, byte[]? socketAddress, ref int socketAddressLen, out int bytesReceived, out SocketFlags receivedFlags, out SocketError errorCode) { try @@ -747,7 +849,8 @@ public static bool TryCompleteSendTo(SafeSocketHandle socket, ReadOnlySpan { sent = buffers != null ? Send(socket, flags, buffers, ref bufferIndex, ref offset, socketAddress, socketAddressLen, out errno) : - Send(socket, flags, buffer, ref offset, ref count, socketAddress, socketAddressLen, out errno); + socketAddress == null ? Send(socket, flags, buffer, ref offset, ref count, out errno) : + Send(socket, flags, buffer, ref offset, ref count, socketAddress, socketAddressLen, out errno); } catch (ObjectDisposedException) { From 98614002d83b63a242f021c0d0ac8b83ffe997ee Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Mon, 18 May 2020 14:57:14 +0200 Subject: [PATCH 2/7] Remove ref from Receive calls --- .../System.Net.Sockets/src/System/Net/Sockets/Socket.cs | 2 +- .../src/System/Net/Sockets/SocketAsyncContext.Unix.cs | 6 +++--- .../src/System/Net/Sockets/SocketPal.Unix.cs | 8 ++++---- .../src/System/Net/Sockets/SocketPal.Windows.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs index 2e3f3d50ada638..a27d95cf957394 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs @@ -1595,7 +1595,7 @@ public int Receive(IList> buffers, SocketFlags socketFlags, o if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"SRC:{LocalEndPoint} DST:{RemoteEndPoint}"); int bytesTransferred; - errorCode = SocketPal.Receive(_handle, buffers, ref socketFlags, out bytesTransferred); + errorCode = SocketPal.Receive(_handle, buffers, socketFlags, out bytesTransferred); #if TRACE_VERBOSE if (NetEventSource.IsEnabled) 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 e9a1fb9ecb497c..d19353e696571c 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 @@ -1473,13 +1473,13 @@ public SocketError ConnectAsync(byte[] socketAddress, int socketAddressLen, Acti return SocketError.IOPending; } - public SocketError Receive(Memory buffer, ref SocketFlags flags, int timeout, out int bytesReceived) + public SocketError Receive(Memory buffer, SocketFlags flags, int timeout, out int bytesReceived) { int socketAddressLen = 0; return ReceiveFrom(buffer, ref flags, null, ref socketAddressLen, timeout, out bytesReceived); } - public SocketError Receive(Span buffer, ref SocketFlags flags, int timeout, out int bytesReceived) + public SocketError Receive(Span buffer, SocketFlags flags, int timeout, out int bytesReceived) { int socketAddressLen = 0; return ReceiveFrom(buffer, ref flags, null, ref socketAddressLen, timeout, out bytesReceived); @@ -1621,7 +1621,7 @@ public SocketError ReceiveFromAsync(Memory buffer, SocketFlags flags, byt return SocketError.IOPending; } - public SocketError Receive(IList> buffers, ref SocketFlags flags, int timeout, out int bytesReceived) + public SocketError Receive(IList> buffers, SocketFlags flags, int timeout, out int bytesReceived) { return ReceiveFrom(buffers, ref flags, null, 0, timeout, out bytesReceived); } diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs index 43cb15667047bf..9b77d4807f46bd 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs @@ -1112,12 +1112,12 @@ public static SocketError SendTo(SafeSocketHandle handle, byte[] buffer, int off return errorCode; } - public static SocketError Receive(SafeSocketHandle handle, IList> buffers, ref SocketFlags socketFlags, out int bytesTransferred) + public static SocketError Receive(SafeSocketHandle handle, IList> buffers, SocketFlags socketFlags, out int bytesTransferred) { SocketError errorCode; if (!handle.IsNonBlocking) { - errorCode = handle.AsyncContext.Receive(buffers, ref socketFlags, handle.ReceiveTimeout, out bytesTransferred); + errorCode = handle.AsyncContext.Receive(buffers, socketFlags, handle.ReceiveTimeout, out bytesTransferred); } else { @@ -1135,7 +1135,7 @@ public static SocketError Receive(SafeSocketHandle handle, byte[] buffer, int of { if (!handle.IsNonBlocking) { - return handle.AsyncContext.Receive(new Memory(buffer, offset, count), ref socketFlags, handle.ReceiveTimeout, out bytesTransferred); + return handle.AsyncContext.Receive(new Memory(buffer, offset, count), socketFlags, handle.ReceiveTimeout, out bytesTransferred); } int socketAddressLen = 0; @@ -1148,7 +1148,7 @@ public static SocketError Receive(SafeSocketHandle handle, Span buffer, So { if (!handle.IsNonBlocking) { - return handle.AsyncContext.Receive(buffer, ref socketFlags, handle.ReceiveTimeout, out bytesTransferred); + return handle.AsyncContext.Receive(buffer, socketFlags, handle.ReceiveTimeout, out bytesTransferred); } int socketAddressLen = 0; diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs index ed39e9be08b67d..6ea3e1cc03aa21 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs @@ -338,7 +338,7 @@ public static unsafe SocketError SendTo(SafeSocketHandle handle, byte[] buffer, return SocketError.Success; } - public static SocketError Receive(SafeSocketHandle handle, IList> buffers, ref SocketFlags socketFlags, out int bytesTransferred) + public static SocketError Receive(SafeSocketHandle handle, IList> buffers, SocketFlags socketFlags, out int bytesTransferred) { const int StackThreshold = 16; // arbitrary limit to avoid too much space on stack (note: may be over-sized, that's OK - length passed separately) int count = buffers.Count; From e8a6e68c956272dbaee0ed581cfd611fe13eb947 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Mon, 18 May 2020 15:37:25 +0200 Subject: [PATCH 3/7] Prefix Pal methods invoking methods with Sys --- .../src/System/Net/Sockets/SocketPal.Unix.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs index 9b77d4807f46bd..cabbcd3866ad57 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs @@ -96,7 +96,7 @@ public static unsafe SocketError CreateSocket(AddressFamily addressFamily, Socke return errorCode; } - private static unsafe int Receive(SafeSocketHandle socket, SocketFlags flags, Span buffer, out Interop.Error errno) + private static unsafe int SysReceive(SafeSocketHandle socket, SocketFlags flags, Span buffer, out Interop.Error errno) { int received = 0; @@ -118,7 +118,7 @@ private static unsafe int Receive(SafeSocketHandle socket, SocketFlags flags, Sp return received; } - private static unsafe int Receive(SafeSocketHandle socket, SocketFlags flags, Span buffer, byte[]? socketAddress, ref int socketAddressLen, out SocketFlags receivedFlags, out Interop.Error errno) + private static unsafe int SysReceive(SafeSocketHandle socket, SocketFlags flags, Span buffer, byte[]? socketAddress, ref int socketAddressLen, out SocketFlags receivedFlags, out Interop.Error errno) { Debug.Assert(socketAddress != null || socketAddressLen == 0, $"Unexpected values: socketAddress={socketAddress}, socketAddressLen={socketAddressLen}"); @@ -159,7 +159,7 @@ private static unsafe int Receive(SafeSocketHandle socket, SocketFlags flags, Sp return checked((int)received); } - private static unsafe int Send(SafeSocketHandle socket, SocketFlags flags, ReadOnlySpan buffer, ref int offset, ref int count, out Interop.Error errno) + private static unsafe int SysSend(SafeSocketHandle socket, SocketFlags flags, ReadOnlySpan buffer, ref int offset, ref int count, out Interop.Error errno) { int sent; fixed (byte* b = &MemoryMarshal.GetReference(buffer)) @@ -185,7 +185,7 @@ private static unsafe int Send(SafeSocketHandle socket, SocketFlags flags, ReadO return sent; } - private static unsafe int Send(SafeSocketHandle socket, SocketFlags flags, ReadOnlySpan buffer, ref int offset, ref int count, byte[] socketAddress, int socketAddressLen, out Interop.Error errno) + private static unsafe int SysSend(SafeSocketHandle socket, SocketFlags flags, ReadOnlySpan buffer, ref int offset, ref int count, byte[] socketAddress, int socketAddressLen, out Interop.Error errno) { int sent; fixed (byte* sockAddr = socketAddress) @@ -225,7 +225,7 @@ private static unsafe int Send(SafeSocketHandle socket, SocketFlags flags, ReadO return sent; } - private static unsafe int Send(SafeSocketHandle socket, SocketFlags flags, IList> buffers, ref int bufferIndex, ref int offset, byte[]? socketAddress, int socketAddressLen, out Interop.Error errno) + private static unsafe int SysSend(SafeSocketHandle socket, SocketFlags flags, IList> buffers, ref int bufferIndex, ref int offset, byte[]? socketAddress, int socketAddressLen, out Interop.Error errno) { // Pin buffers and set up iovecs. int startIndex = bufferIndex, startOffset = offset; @@ -322,7 +322,7 @@ private static unsafe long SendFile(SafeSocketHandle socket, SafeFileHandle file return bytesSent; } - private static unsafe int Receive(SafeSocketHandle socket, SocketFlags flags, IList> buffers, byte[]? socketAddress, ref int socketAddressLen, out SocketFlags receivedFlags, out Interop.Error errno) + private static unsafe int SysReceive(SafeSocketHandle socket, SocketFlags flags, IList> buffers, byte[]? socketAddress, ref int socketAddressLen, out SocketFlags receivedFlags, out Interop.Error errno) { int available = 0; errno = Interop.Sys.GetBytesAvailable(socket, &available); @@ -421,7 +421,7 @@ private static unsafe int Receive(SafeSocketHandle socket, SocketFlags flags, IL return checked((int)received); } - private static unsafe int ReceiveMessageFrom(SafeSocketHandle socket, SocketFlags flags, Span buffer, byte[] socketAddress, ref int socketAddressLen, bool isIPv4, bool isIPv6, out SocketFlags receivedFlags, out IPPacketInformation ipPacketInformation, out Interop.Error errno) + private static unsafe int SysReceiveMessageFrom(SafeSocketHandle socket, SocketFlags flags, Span buffer, byte[] socketAddress, ref int socketAddressLen, bool isIPv4, bool isIPv6, out SocketFlags receivedFlags, out IPPacketInformation ipPacketInformation, out Interop.Error errno) { Debug.Assert(socketAddress != null, "Expected non-null socketAddress"); @@ -471,7 +471,7 @@ private static unsafe int ReceiveMessageFrom(SafeSocketHandle socket, SocketFlag return checked((int)received); } - private static unsafe int ReceiveMessageFrom( + private static unsafe int SysReceiveMessageFrom( SafeSocketHandle socket, SocketFlags flags, IList> buffers, byte[] socketAddress, ref int socketAddressLen, bool isIPv4, bool isIPv6, out SocketFlags receivedFlags, out IPPacketInformation ipPacketInformation, out Interop.Error errno) @@ -679,7 +679,7 @@ public static unsafe bool TryCompleteReceive(SafeSocketHandle socket, Span // however complete a 0-byte read successfully when data isn't available, as the request can logically be satisfied // synchronously. As such, we treat 0 specially, and perform a 1-byte peek. byte oneBytePeekBuffer; - received = Receive(socket, flags | SocketFlags.Peek, new Span(&oneBytePeekBuffer, 1), out errno); + received = SysReceive(socket, flags | SocketFlags.Peek, new Span(&oneBytePeekBuffer, 1), out errno); if (received > 0) { // Peeked for 1-byte, but the actual request was for 0. @@ -689,7 +689,7 @@ public static unsafe bool TryCompleteReceive(SafeSocketHandle socket, Span else { // Receive > 0 bytes into a single buffer - received = Receive(socket, flags, buffer, out errno); + received = SysReceive(socket, flags, buffer, out errno); } if (received != -1) @@ -729,7 +729,7 @@ public static unsafe bool TryCompleteReceiveFrom(SafeSocketHandle socket, Span(&oneBytePeekBuffer, 1), socketAddress, ref socketAddressLen, out receivedFlags, out errno); + received = SysReceive(socket, flags | SocketFlags.Peek, new Span(&oneBytePeekBuffer, 1), socketAddress, ref socketAddressLen, out receivedFlags, out errno); if (received > 0) { // Peeked for 1-byte, but the actual request was for 0. @@ -748,7 +748,7 @@ public static unsafe bool TryCompleteReceiveFrom(SafeSocketHandle socket, Span 0 bytes into a single buffer - received = Receive(socket, flags, buffer, socketAddress, ref socketAddressLen, out receivedFlags, out errno); + received = SysReceive(socket, flags, buffer, socketAddress, ref socketAddressLen, out receivedFlags, out errno); } if (received != -1) @@ -786,8 +786,8 @@ public static unsafe bool TryCompleteReceiveMessageFrom(SafeSocketHandle socket, Interop.Error errno; int received = buffers == null ? - ReceiveMessageFrom(socket, flags, buffer, socketAddress, ref socketAddressLen, isIPv4, isIPv6, out receivedFlags, out ipPacketInformation, out errno) : - ReceiveMessageFrom(socket, flags, buffers, socketAddress, ref socketAddressLen, isIPv4, isIPv6, out receivedFlags, out ipPacketInformation, out errno); + SysReceiveMessageFrom(socket, flags, buffer, socketAddress, ref socketAddressLen, isIPv4, isIPv6, out receivedFlags, out ipPacketInformation, out errno) : + SysReceiveMessageFrom(socket, flags, buffers, socketAddress, ref socketAddressLen, isIPv4, isIPv6, out receivedFlags, out ipPacketInformation, out errno); if (received != -1) { @@ -848,9 +848,9 @@ public static bool TryCompleteSendTo(SafeSocketHandle socket, ReadOnlySpan try { sent = buffers != null ? - Send(socket, flags, buffers, ref bufferIndex, ref offset, socketAddress, socketAddressLen, out errno) : - socketAddress == null ? Send(socket, flags, buffer, ref offset, ref count, out errno) : - Send(socket, flags, buffer, ref offset, ref count, socketAddress, socketAddressLen, out errno); + SysSend(socket, flags, buffers, ref bufferIndex, ref offset, socketAddress, socketAddressLen, out errno) : + socketAddress == null ? SysSend(socket, flags, buffer, ref offset, ref count, out errno) : + SysSend(socket, flags, buffer, ref offset, ref count, socketAddress, socketAddressLen, out errno); } catch (ObjectDisposedException) { From 1a5457e7f0ebb4748b8f7f5be0413fb701014d11 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Mon, 18 May 2020 15:41:17 +0200 Subject: [PATCH 4/7] Also use single-buffer syscalls for sync Socket Receive methods --- .../src/System/Net/Sockets/SocketPal.Unix.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs index cabbcd3866ad57..405fdf242becb4 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs @@ -1138,9 +1138,8 @@ public static SocketError Receive(SafeSocketHandle handle, byte[] buffer, int of return handle.AsyncContext.Receive(new Memory(buffer, offset, count), socketFlags, handle.ReceiveTimeout, out bytesTransferred); } - int socketAddressLen = 0; SocketError errorCode; - bool completed = TryCompleteReceiveFrom(handle, new Span(buffer, offset, count), socketFlags, null, ref socketAddressLen, out bytesTransferred, out socketFlags, out errorCode); + bool completed = TryCompleteReceive(handle, new Span(buffer, offset, count), socketFlags, out bytesTransferred, out errorCode); return completed ? errorCode : SocketError.WouldBlock; } @@ -1151,9 +1150,8 @@ public static SocketError Receive(SafeSocketHandle handle, Span buffer, So return handle.AsyncContext.Receive(buffer, socketFlags, handle.ReceiveTimeout, out bytesTransferred); } - int socketAddressLen = 0; SocketError errorCode; - bool completed = TryCompleteReceiveFrom(handle, buffer, socketFlags, null, ref socketAddressLen, out bytesTransferred, out socketFlags, out errorCode); + bool completed = TryCompleteReceive(handle, buffer, socketFlags, out bytesTransferred, out errorCode); return completed ? errorCode : SocketError.WouldBlock; } From aca1da97bd5708becc3b94c3e3591b0933b96725 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Mon, 18 May 2020 15:48:47 +0200 Subject: [PATCH 5/7] Improve comment --- .../src/System/Net/Sockets/SocketAsyncContext.Unix.cs | 1 + .../src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) 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 d19353e696571c..22b340843d21c1 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 @@ -482,6 +482,7 @@ protected override bool DoTryComplete(SocketAsyncContext context) { if (!SetReceivedFlags && SocketAddress == null) { + ReceivedFlags = SocketFlags.None; return SocketPal.TryCompleteReceive(context._socket, Buffer.Span, Flags, out BytesTransferred, out ErrorCode); } else diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs index 5083c56bcb2030..99147f05a429c2 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs @@ -128,10 +128,12 @@ internal unsafe SocketError DoOperationReceive(SafeSocketHandle handle, Cancella SocketError errorCode; if (_bufferList == null) { - if (_currentSocket!.ProtocolType == ProtocolType.Tcp) // no out-going socket flags. + // TCP has no out-going receive flags. We can use different syscalls which give better performance. + bool noReceivedFlags = _currentSocket!.ProtocolType == ProtocolType.Tcp; + if (noReceivedFlags) { - flags = SocketFlags.None; errorCode = handle.AsyncContext.ReceiveAsync(_buffer.Slice(_offset, _count), _socketFlags, out bytesReceived, TransferCompletionCallback, cancellationToken); + flags = SocketFlags.None; } else { From 4cb706b260aa8444b15ff9ecaf3607e3fbd04a3c Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 19 May 2020 14:10:49 +0200 Subject: [PATCH 6/7] PR feedback --- src/libraries/Native/Unix/System.Native/pal_networking.c | 4 ++-- .../src/System/Net/Sockets/SocketPal.Unix.cs | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/libraries/Native/Unix/System.Native/pal_networking.c b/src/libraries/Native/Unix/System.Native/pal_networking.c index 305725a09797fc..fab997cf0b3b31 100644 --- a/src/libraries/Native/Unix/System.Native/pal_networking.c +++ b/src/libraries/Native/Unix/System.Native/pal_networking.c @@ -1435,7 +1435,7 @@ int32_t SystemNative_Send(intptr_t socket, void* buffer, int32_t bufferLen, int3 ssize_t res; #if defined(__APPLE__) && __APPLE__ - // possible OSX kernel bug: #31927 + // possible OSX kernel bug: https://github.com/dotnet/runtime/issues/27221 while ((res = send(fd, buffer, (size_t)bufferLen, socketFlags)) < 0 && (errno == EINTR || errno == EPROTOTYPE)); #else while ((res = send(fd, buffer, (size_t)bufferLen, socketFlags)) < 0 && errno == EINTR); @@ -1471,7 +1471,7 @@ int32_t SystemNative_SendMessage(intptr_t socket, MessageHeader* messageHeader, ssize_t res; #if defined(__APPLE__) && __APPLE__ - // possible OSX kernel bug: #31927 + // possible OSX kernel bug: https://github.com/dotnet/runtime/issues/27221 while ((res = sendmsg(fd, &header, socketFlags)) < 0 && (errno == EINTR || errno == EPROTOTYPE)); #else while ((res = sendmsg(fd, &header, socketFlags)) < 0 && errno == EINTR); diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs index 405fdf242becb4..1a559641e64507 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs @@ -164,15 +164,12 @@ private static unsafe int SysSend(SafeSocketHandle socket, SocketFlags flags, Re int sent; fixed (byte* b = &MemoryMarshal.GetReference(buffer)) { - int bytesSent = 0; errno = Interop.Sys.Send( socket, &b[offset], count, flags, - &bytesSent); - - sent = bytesSent; + &sent); } if (errno != Interop.Error.SUCCESS) From 0ea69d631f3a99f8a53369148514c9f1660da721 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 19 May 2020 17:32:15 +0200 Subject: [PATCH 7/7] Assert SocketAddress null instead of checking --- .../src/System/Net/Sockets/SocketAsyncContext.Unix.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 22b340843d21c1..9934e5c97f6560 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 @@ -480,8 +480,10 @@ protected override bool DoTryComplete(SocketAsyncContext context) } else { - if (!SetReceivedFlags && SocketAddress == null) + if (!SetReceivedFlags) { + Debug.Assert(SocketAddress == null); + ReceivedFlags = SocketFlags.None; return SocketPal.TryCompleteReceive(context._socket, Buffer.Span, Flags, out BytesTransferred, out ErrorCode); }