mono/mono

SocketTaskExtensions.ReceiveAsync(Memory<T>) always reads zeroes.

Opened this issue · 9 comments

The Memory<T> overload of the SocketTaskExtensions.ReceiveAsync extension method always reads zeroes into the Memory<T>. It should read the bytes read from the network into the Memory<T>.

Steps to Reproduce

  1. Open a Socket
  2. Call the Memory<T> overload of the ReceiveAsync function
  3. Observe it returning the correct amount of bytes read, but reading all zeroes into the Memory<T>.

Taking a look at the Mono implementation of this overload makes the issue pretty obvious.

public static ValueTask<int> ReceiveAsync(this Socket socket, Memory<byte> memory, SocketFlags socketFlags, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<int>(socket);
socket.BeginReceive(memory.ToArray(), 0, memory.Length, socketFlags, iar =>
{
cancellationToken.ThrowIfCancellationRequested();
var tcsInner = (TaskCompletionSource<int>)iar.AsyncState;
var socketInner = (Socket)tcsInner.Task.AsyncState;
try { tcsInner.TrySetResult(socketInner.EndReceive(iar)); }
catch (Exception exc) { tcsInner.TrySetException(exc); }
}, tcs);
cancellationToken.ThrowIfCancellationRequested();
return new ValueTask<int>(tcs.Task);
}

The current implementation creates a copy of the Memory<T> by way of ToArray(), but doesn't store a reference to it. The BeginReceive is essentially reading into a temporary array that is thrown away at the end of the function call, so the result never ends up in the Memory<T>.

Has anyone had a chance to triage this? This is still very much an issue in production for us, preventing us from using a single shared binary between our mobile and desktop targets.

I met this bug too on unity 2021

I changed Memory<byte> to ArraySegmen<byte>, solved this problem.

ArraySegment<byte> buffer = new(readBuffer, 0, 1024);
int count = await _socket.ReceiveAsync(buffer, SocketFlags.None);

I changed Memory to ArraySegmen, solved this problem.

ArraySegment<byte> buffer = new(readBuffer, 0, 1024);
int count = await _socket.ReceiveAsync(buffer, SocketFlags.None);

Reading it into an ArraySegment doesn't really solve this problem, as you're still allocating memory. The idea behind the Memory<T> overload is to avoid a memory allocation. This is no better than just allocating a byte[] and using that to receive into.

Any progress on triaging this? Moderately significant gotcha.

True for Unity 2022.3 😬

As a solution you can patch this method, here how this was made in one project: openmod/openmod#763

Just tapping back here - apparently this issue doesn't happen on MacOS with Unity 2022.3.22f1

mtmk commented

Re Unity: This is only an issue I saw in 2021.3.29f1. When I tried on 2022.3.36f1 All worked fine. (still and issue with mono 6.12, but Unity folks must've patched it in later releases)

tested with mono 6.12 on Windows 32/64 and macOS: problem is still there!

edit mono repro here: nats-io/nats.net#306 (comment)