Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds synchronous span APIs for datagram sockets. #51956

Merged
merged 4 commits into from
May 10, 2021
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
4 changes: 4 additions & 0 deletions src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ public void Listen(int backlog) { }
public int ReceiveFrom(byte[] buffer, int size, System.Net.Sockets.SocketFlags socketFlags, ref System.Net.EndPoint remoteEP) { throw null; }
public int ReceiveFrom(byte[] buffer, ref System.Net.EndPoint remoteEP) { throw null; }
public int ReceiveFrom(byte[] buffer, System.Net.Sockets.SocketFlags socketFlags, ref System.Net.EndPoint remoteEP) { throw null; }
public int ReceiveFrom(System.Span<byte> buffer, ref System.Net.EndPoint remoteEP) { throw null; }
public int ReceiveFrom(System.Span<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, ref System.Net.EndPoint remoteEP) { throw null; }
public System.Threading.Tasks.Task<System.Net.Sockets.SocketReceiveFromResult> ReceiveFromAsync(System.ArraySegment<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEndPoint) { throw null; }
public System.Threading.Tasks.ValueTask<System.Net.Sockets.SocketReceiveFromResult> ReceiveFromAsync(System.Memory<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEndPoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public bool ReceiveFromAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; }
Expand Down Expand Up @@ -417,6 +419,8 @@ public void SendFile(string? fileName, System.ReadOnlySpan<byte> preBuffer, Syst
public int SendTo(byte[] buffer, int size, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
public int SendTo(byte[] buffer, System.Net.EndPoint remoteEP) { throw null; }
public int SendTo(byte[] buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
public int SendTo(System.ReadOnlySpan<byte> buffer, System.Net.EndPoint remoteEP) { throw null; }
public int SendTo(System.ReadOnlySpan<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
public System.Threading.Tasks.Task<int> SendToAsync(System.ArraySegment<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; }
public System.Threading.Tasks.ValueTask<int> SendToAsync(System.ReadOnlyMemory<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public bool SendToAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; }
Expand Down
148 changes: 148 additions & 0 deletions src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,67 @@ public int SendTo(byte[] buffer, EndPoint remoteEP)
return SendTo(buffer, 0, buffer != null ? buffer.Length : 0, SocketFlags.None, remoteEP);
}

/// <summary>
/// Sends data to the specified endpoint.
/// </summary>
/// <param name="buffer">A span of bytes that contains the data to be sent.</param>
/// <param name="remoteEP">The <see cref="EndPoint"/> that represents the destination for the data.</param>
/// <returns>The number of bytes sent.</returns>
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
/// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
public int SendTo(ReadOnlySpan<byte> buffer, EndPoint remoteEP)
{
return SendTo(buffer, SocketFlags.None, remoteEP);
}

/// <summary>
/// Sends data to a specific endpoint using the specified <see cref="SocketFlags"/>.
/// </summary>
/// <param name="buffer">A span of bytes that contains the data to be sent.</param>
/// <param name="socketFlags">A bitwise combination of the <see cref="SocketFlags"/> values.</param>
/// <param name="remoteEP">The <see cref="EndPoint"/> that represents the destination for the data.</param>
/// <returns>The number of bytes sent.</returns>
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
/// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
public int SendTo(ReadOnlySpan<byte> buffer, SocketFlags socketFlags, EndPoint remoteEP)
{
ThrowIfDisposed();
if (remoteEP == null)
{
throw new ArgumentNullException(nameof(remoteEP));
}

ValidateBlockingMode();

Copy link
Member

Choose a reason for hiding this comment

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

I assume the if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(...); has been omitted here, and at other places, because it's also missing from the Send(ROS) overload.

@geoffkizer (and maybe @MihaZupan) is NetEventSource an obsolete thing we are leaving behind in (functionally identical) span overloads on purpose, or is this a mistake? If it's a mistake is this something we care about and want to fix?

Here's how the same code looks like on the byte[] path:

public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP)
{
ThrowIfDisposed();
ValidateBufferArguments(buffer, offset, size);
if (remoteEP == null)
{
throw new ArgumentNullException(nameof(remoteEP));
}
ValidateBlockingMode();
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"SRC:{LocalEndPoint} size:{size} remoteEP:{remoteEP}");
Internals.SocketAddress socketAddress = Serialize(ref remoteEP);

Copy link
Contributor

Choose a reason for hiding this comment

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

NetEventSource is whatever we want for internal logging purposes. It's not intended to be public, ever. To the extent that it duplicates our public telemetry, we should remove it. But there's a lot of internal logging code that has proven useful in the past, and we don't want to just blindly rip this out.

Copy link
Member

Choose a reason for hiding this comment

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

OK, from what you said it feels like it's not worth opening an issue for it, but we should keep it in mind. A dedupe refactor across the span/array overloads should fix this anyways.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep.

A dedupe refactor across the span/array overloads should fix this anyways.

This is blocked on #46600 right?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, with #46600 we would get rid of synchronous byte[] overloads on SocketAsyncContext, and rebuild byte[] overloads on top of Span<byte> overloads in Socket.cs.

Internals.SocketAddress socketAddress = Serialize(ref remoteEP);

int bytesTransferred;
SocketError errorCode = SocketPal.SendTo(_handle, buffer, socketFlags, socketAddress.Buffer, socketAddress.Size, out bytesTransferred);

// Throw an appropriate SocketException if the native call fails.
if (errorCode != SocketError.Success)
{
UpdateSendSocketErrorForDisposed(ref errorCode);

UpdateStatusAfterSocketErrorAndThrowException(errorCode);
}
else if (SocketsTelemetry.Log.IsEnabled())
{
SocketsTelemetry.Log.BytesSent(bytesTransferred);
if (SocketType == SocketType.Dgram) SocketsTelemetry.Log.DatagramSent();
}

if (_rightEndPoint == null)
{
// Save a copy of the EndPoint so we can use it for Create().
_rightEndPoint = remoteEP;
}

return bytesTransferred;
}

// Receives data from a connected socket.
public int Receive(byte[] buffer, int size, SocketFlags socketFlags)
{
Expand Down Expand Up @@ -1764,6 +1825,93 @@ public int ReceiveFrom(byte[] buffer, ref EndPoint remoteEP)
return ReceiveFrom(buffer, 0, buffer != null ? buffer.Length : 0, SocketFlags.None, ref remoteEP);
}

/// <summary>
/// Receives a datagram into the data buffer and stores the endpoint.
/// </summary>
/// <param name="buffer">A span of bytes that is the storage location for received data.</param>
/// <param name="remoteEP">An <see cref="EndPoint"/>, passed by reference, that represents the remote server.</param>
/// <returns>The number of bytes received.</returns>
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
/// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
public int ReceiveFrom(Span<byte> buffer, ref EndPoint remoteEP)
{
return ReceiveFrom(buffer, SocketFlags.None, ref remoteEP);
}

/// <summary>
/// Receives a datagram into the data buffer, using the specified <see cref="SocketFlags"/>, and stores the endpoint.
/// </summary>
/// <param name="buffer">A span of bytes that is the storage location for received data.</param>
/// <param name="socketFlags">A bitwise combination of the <see cref="SocketFlags"/> values.</param>
/// <param name="remoteEP">An <see cref="EndPoint"/>, passed by reference, that represents the remote server.</param>
/// <returns>The number of bytes received.</returns>
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is <see langword="null" />.</exception>
/// <exception cref="SocketException">An error occurred when attempting to access the socket.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
public int ReceiveFrom(Span<byte> buffer, SocketFlags socketFlags, ref EndPoint remoteEP)
{
ThrowIfDisposed();
ValidateReceiveFromEndpointAndState(remoteEP, nameof(remoteEP));

SocketPal.CheckDualModeReceiveSupport(this);

ValidateBlockingMode();

// We don't do a CAS demand here because the contents of remoteEP aren't used by
// WSARecvFrom; all that matters is that we generate a unique-to-this-call SocketAddress
// with the right address family.
EndPoint endPointSnapshot = remoteEP;
Internals.SocketAddress socketAddress = Serialize(ref endPointSnapshot);
Internals.SocketAddress socketAddressOriginal = IPEndPointExtensions.Serialize(endPointSnapshot);

int bytesTransferred;
SocketError errorCode = SocketPal.ReceiveFrom(_handle, buffer, socketFlags, socketAddress.Buffer, ref socketAddress.InternalSize, out bytesTransferred);

UpdateReceiveSocketErrorForDisposed(ref errorCode, bytesTransferred);
// If the native call fails we'll throw a SocketException.
SocketException? socketException = null;
if (errorCode != SocketError.Success)
{
socketException = new SocketException((int)errorCode);
UpdateStatusAfterSocketError(socketException);
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, socketException);

if (socketException.SocketErrorCode != SocketError.MessageSize)
{
throw socketException;
}
}
else if (SocketsTelemetry.Log.IsEnabled())
{
SocketsTelemetry.Log.BytesReceived(bytesTransferred);
if (SocketType == SocketType.Dgram) SocketsTelemetry.Log.DatagramReceived();
}

if (!socketAddressOriginal.Equals(socketAddress))
{
try
{
remoteEP = endPointSnapshot.Create(socketAddress);
}
catch
{
}
if (_rightEndPoint == null)
{
// Save a copy of the EndPoint so we can use it for Create().
_rightEndPoint = endPointSnapshot;
}
}

if (socketException != null)
{
throw socketException;
}

return bytesTransferred;
}

public int IOControl(int ioControlCode, byte[]? optionInValue, byte[]? optionOutValue)
{
ThrowIfDisposed();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,19 @@ public static SocketError SendTo(SafeSocketHandle handle, byte[] buffer, int off
return errorCode;
}

public static SocketError SendTo(SafeSocketHandle handle, ReadOnlySpan<byte> buffer, SocketFlags socketFlags, byte[] socketAddress, int socketAddressLen, out int bytesTransferred)
{
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.SendTo(buffer, socketFlags, socketAddress, socketAddressLen, handle.SendTimeout, out bytesTransferred);
}

bytesTransferred = 0;
SocketError errorCode;
TryCompleteSendTo(handle, buffer, socketFlags, socketAddress, socketAddressLen, ref bytesTransferred, out errorCode);
return errorCode;
}

public static SocketError Receive(SafeSocketHandle handle, IList<ArraySegment<byte>> buffers, SocketFlags socketFlags, out int bytesTransferred)
{
SocketError errorCode;
Expand Down Expand Up @@ -1311,6 +1324,18 @@ public static SocketError ReceiveFrom(SafeSocketHandle handle, byte[] buffer, in
return completed ? errorCode : SocketError.WouldBlock;
}

public static SocketError ReceiveFrom(SafeSocketHandle handle, Span<byte> buffer, SocketFlags socketFlags, byte[] socketAddress, ref int socketAddressLen, out int bytesTransferred)
{
if (!handle.IsNonBlocking)
{
return handle.AsyncContext.ReceiveFrom(buffer, ref socketFlags, socketAddress, ref socketAddressLen, handle.ReceiveTimeout, out bytesTransferred);
}

SocketError errorCode;
bool completed = TryCompleteReceiveFrom(handle, buffer, socketFlags, socketAddress, ref socketAddressLen, out bytesTransferred, out socketFlags, out errorCode);
return completed ? errorCode : SocketError.WouldBlock;
}

public static SocketError WindowsIoctl(SafeSocketHandle handle, int ioControlCode, byte[]? optionInValue, byte[]? optionOutValue, out int optionLength)
{
// Three codes are called out in the Winsock IOCTLs documentation as "The following Unix IOCTL codes (commands) are supported." They are
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,31 +300,15 @@ public static unsafe SocketError SendFile(SafeSocketHandle handle, SafeFileHandl
}
}

public static unsafe SocketError SendTo(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] peerAddress, int peerAddressSize, out int bytesTransferred)
public static SocketError SendTo(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] peerAddress, int peerAddressSize, out int bytesTransferred) =>
SendTo(handle, buffer.AsSpan(offset, size), socketFlags, peerAddress, peerAddressSize, out bytesTransferred);

public static unsafe SocketError SendTo(SafeSocketHandle handle, ReadOnlySpan<byte> buffer, SocketFlags socketFlags, byte[] peerAddress, int peerAddressSize, out int bytesTransferred)
{
int bytesSent;
if (buffer.Length == 0)
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
PJB3005 marked this conversation as resolved.
Show resolved Hide resolved
{
bytesSent = Interop.Winsock.sendto(
handle,
null,
0,
socketFlags,
peerAddress,
peerAddressSize);
}
else
{
fixed (byte* pinnedBuffer = &buffer[0])
{
bytesSent = Interop.Winsock.sendto(
handle,
pinnedBuffer + offset,
size,
socketFlags,
peerAddress,
peerAddressSize);
}
bytesSent = Interop.Winsock.sendto(handle, bufferPtr, buffer.Length, socketFlags, peerAddress, peerAddressSize);
}

if (bytesSent == (int)SocketError.SocketError)
Expand Down Expand Up @@ -528,19 +512,16 @@ public static unsafe SocketError ReceiveMessageFrom(Socket socket, SafeSocketHan
return SocketError.Success;
}

public static unsafe SocketError ReceiveFrom(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] socketAddress, ref int addressLength, out int bytesTransferred)
public static unsafe SocketError ReceiveFrom(SafeSocketHandle handle, byte[] buffer, int offset, int size, SocketFlags socketFlags, byte[] socketAddress, ref int addressLength, out int bytesTransferred) =>
ReceiveFrom(handle, buffer.AsSpan(offset, size), SocketFlags.None, socketAddress, ref addressLength, out bytesTransferred);

public static unsafe SocketError ReceiveFrom(SafeSocketHandle handle, Span<byte> buffer, SocketFlags socketFlags, byte[] socketAddress, ref int addressLength, out int bytesTransferred)
{
int bytesReceived;
if (buffer.Length == 0)
{
bytesReceived = Interop.Winsock.recvfrom(handle, null, 0, socketFlags, socketAddress, ref addressLength);
}
else

fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
PJB3005 marked this conversation as resolved.
Show resolved Hide resolved
{
fixed (byte* pinnedBuffer = &buffer[0])
{
bytesReceived = Interop.Winsock.recvfrom(handle, pinnedBuffer + offset, size, socketFlags, socketAddress, ref addressLength);
}
bytesReceived = Interop.Winsock.recvfrom(handle, bufferPtr, buffer.Length, socketFlags, socketAddress, ref addressLength);
}

if (bytesReceived == (int)SocketError.SocketError)
Expand Down
Loading