Skip to content

Conversation

@geoffkizer
Copy link
Contributor

@geoffkizer geoffkizer commented Apr 19, 2021

Fixes #51426

@ghost
Copy link

ghost commented Apr 19, 2021

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details
Author: geoffkizer
Assignees: -
Labels:

area-System.Net.Sockets

Milestone: -

@stephentoub
Copy link
Member

I assume this is related to #51426?

Sure enough:


Error message
System.Net.Sockets.SocketException : Bad address


Stack trace
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.CreateException(SocketError error, Boolean forAsyncThrow) in /_/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs:line 1287
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.SendAsync(Socket socket, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs:line 984
   at System.Net.Sockets.Socket.SendAsync(ReadOnlyMemory`1 buffer, SocketFlags socketFlags, CancellationToken cancellationToken) in /_/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs:line 470
   at System.Net.Sockets.Tests.SendReceive`1.<Send_0ByteSend_Memory_Success>d__9[[System.Net.Sockets.Tests.SocketHelperTask, System.Net.Sockets.Tests, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51]].MoveNext() in /_/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive/SendReceive.cs:line 630
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Net.Sockets.Tests.SendReceive`1.<Send_0ByteSend_Memory_Success>d__9[[System.Net.Sockets.Tests.SocketHelperTask, System.Net.Sockets.Tests, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51]], System.Net.Sockets.Tests, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51]].ExecutionContextCallback(Object s) in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs:line 286
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) in /_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs:line 183
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Net.Sockets.Tests.SendReceive`1.<Send_0ByteSend_Memory_Success>d__9[[System.Net.Sockets.Tests.SocketHelperTask, System.Net.Sockets.Tests, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51]], System.Net.Sockets.Tests, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51]].MoveNext(Thread threadPoolThread) in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs:line 324
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Net.Sockets.Tests.SendReceive`1.<Send_0ByteSend_Memory_Success>d__9[[System.Net.Sockets.Tests.SocketHelperTask, System.Net.Sockets.Tests, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51]], System.Net.Sockets.Tests, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51]].MoveNext() in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs:line 302
   at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.<>c.<.cctor>b__8_0(Object state) in /_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs:line 370
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) in /_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs:line 183
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) in /_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs:line 147
   at System.Object.lambda_method2(Closure , Object , Object ) in System.Private.CoreLib.dll:token 0x0+0x0
   at System.Threading.Tasks.Task.InnerInvoke() in /_/src/libraries/Sys

@geoffkizer
Copy link
Contributor Author

yep

@geoffkizer geoffkizer changed the title add zero byte send test fix zero byte Send on linux Apr 19, 2021
@geoffkizer geoffkizer marked this pull request as ready for review April 19, 2021 02:28
@geoffkizer
Copy link
Contributor Author

Added code to fix the issue. PTAL.

{
sent = 0;
errno = Interop.Error.SUCCESS;
}
Copy link
Member

Choose a reason for hiding this comment

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

Are there any SocketFlags that this could potentially cause problems for? Wondering if instead of this, we should ensure we still make the syscall so that the OS can do whatever it needs to do, and just substitute in Array.Empty for buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's possible. Your suggestion would be safer, for sure.

On the other hand, if someone does a 0-byte send today, this change will save a syscall.

On the other other hand, don't do 0-byte sends.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

substitute in Array.Empty for buffer

If we did this we'd still need to pin Array.Empty, right? Is that bad?

Copy link
Member

Choose a reason for hiding this comment

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

If we did this we'd still need to pin Array.Empty, right? Is that bad?

For the duration of the synchronous syscall. That's fine.

That said, it doesn't need to be Array.Empty. It could just be a stack address:

byte b = 0;
buffer = MemoryMarshal.CreateReadOnlySpan(ref b, 0);
SysSend(..., buffer, ...);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I pushed a new fix. PTAL.

@geoffkizer
Copy link
Contributor Author

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@geoffkizer
Copy link
Contributor Author

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

// The Linux kernel doesn't like it if we pass a null reference for buffer pointers, even if the length is 0.
// Replace any null pointer (e.g. from Memory<byte>.Empty) with a valid pointer.
private static ReadOnlySpan<byte> AvoidNullReference(ReadOnlySpan<byte> buffer) =>
Unsafe.IsNullRef(ref MemoryMarshal.GetReference(buffer)) ? Array.Empty<byte>() : buffer;
Copy link
Member

Choose a reason for hiding this comment

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

buffer.IsEmpty would work, too. That wouldn't mean it was a null ref, but it wouldn't hurt to sub in Array.Empty for an empty span even if it's not null.

{
sent = SysSend(socket, flags, buffer, ref offset, ref count, socketAddress, socketAddressLen, out errno);
sent = buffers != null ?
SysSend(socket, flags, buffers, ref bufferIndex, ref offset, socketAddress, socketAddressLen, out errno) :
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible the buffers list contains a default ArraySegment, or do we screen those out? Wondering if the same issue might exist there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We validate the buffer list when you set it (BufferList setter) and throw on null ArraySegments.

Copy link
Contributor

@antonfirsov antonfirsov left a comment

Choose a reason for hiding this comment

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

LGTM

Comment on lines +578 to +608
public async Task Send_0ByteSend_Success()
{
using (Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
using (Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
listener.Listen(1);

Task<Socket> acceptTask = AcceptAsync(listener);
await Task.WhenAll(
acceptTask,
ConnectAsync(client, new IPEndPoint(IPAddress.Loopback, ((IPEndPoint)listener.LocalEndPoint).Port)));

using (Socket server = await acceptTask)
{
for (int i = 0; i < 3; i++)
{
// Zero byte send should be a no-op
int bytesSent = await SendAsync(client, new ArraySegment<byte>(Array.Empty<byte>()));
Assert.Equal(0, bytesSent);

// Socket should still be usable
await SendAsync(client, new byte[] { 99 });
byte[] buffer = new byte[10];
int bytesReceived = await ReceiveAsync(server, buffer);
Assert.Equal(1, bytesReceived);
Assert.Equal(99, buffer[0]);
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: it might be worth to dedupe the test code by moving it to a helper method, that can be then invoked from the actual test cases:

protected Task<int> RunZeroByteSendTest(Action<Socket> doSendZeroBytes) { ... }

Even SendReceive_SpanSyncForceNonBlocking could do

RunZeroByteSendTest(client => {
    Task<int> sendTask = client.SendAsync(ReadOnlyMemory<byte>.Empty, SocketFlags.None).AsTask();
    Assert.True(sendTask.IsCompleted);
    return sendTask;
});


using (Socket server = await acceptTask)
{
for (int i = 0; i < 3; i++)
Copy link
Contributor

Choose a reason for hiding this comment

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

Just wondering: what is the behavior that makes it worth running the same test 3 times on the same socket?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nothing in particular; I was mimicking other tests that do similar stuff. I can remove if you think it's weird.

@geoffkizer geoffkizer merged commit 333a6c7 into dotnet:main Apr 20, 2021
@geoffkizer geoffkizer deleted the zerobytesendtest branch April 20, 2021 16:30
@wfurt
Copy link
Member

wfurt commented Apr 20, 2021

is there even any benefit of calling OS if the buffer is empty? I can see it for read but for send I'm not really sure.

@geoffkizer
Copy link
Contributor Author

@wfurt we touched on that above. We decided to be safe and keep the syscall, just in case this has any meaning for a particular socket. If the user cares about avoiding the syscall they can do this themselves.

@karelz karelz added this to the 6.0.0 milestone May 20, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Jun 19, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Socket.SendAsync and ReadOnlyMemory<byte>.Empty throws exception

5 participants