-
Notifications
You must be signed in to change notification settings - Fork 5.3k
fix zero byte Send on linux #51473
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
fix zero byte Send on linux #51473
Conversation
|
Tagging subscribers to this area: @dotnet/ncl Issue Details
|
|
I assume this is related to #51426? Sure enough: |
|
yep |
|
Added code to fix the issue. PTAL. |
| { | ||
| sent = 0; | ||
| errno = Interop.Error.SUCCESS; | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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, ...);There was a problem hiding this comment.
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.
|
/azp run runtime-libraries-coreclr outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
src/libraries/System.Net.Sockets/tests/FunctionalTests/SendReceive/SendReceive.cs
Show resolved
Hide resolved
|
/azp run runtime-libraries-coreclr outerloop |
|
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; |
There was a problem hiding this comment.
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) : |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
antonfirsov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
| 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]); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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++) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
|
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. |
|
@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. |
Fixes #51426