NetSharp

NetSharp.git
git clone git://git.lenczewski.org/NetSharp.git
Log | Files | Refs | README | LICENSE

commit 520db7e9ddc416452bceb18e20ecfba2eaa81846
parent d495b2314b54b3cd04ce467725216f2150f553da
Author: Mikolaj Lenczewski <mikolaj.lenczewski308@gmail.com>
Date:   Sun, 26 Apr 2020 14:11:40 +0100

Improved send memory buffer safety, and short-circuited cancellation token setup if the passed token is its default value.

Diffstat:
MNetSharp/NetSharp/NetSharp.xml | 42++++++++++++++++++++++++++++++++++--------
MNetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs | 75++++++++++++++++++++++++++++++++++++++++++++-------------------------------
MNetSharp/NetSharp/Sockets/SocketClient.cs | 55+++++++++++++++++++++++++++++++++++++++++++------------
MNetSharp/NetSharp/Sockets/Stream/StreamSocketClient.cs | 273++++++++++++++++++++++++++++++++++++++++---------------------------------------
MNetSharp/NetSharpExamples/Benchmarks/UdpSocketClientAsyncBenchmark.cs | 6+++---
5 files changed, 261 insertions(+), 190 deletions(-)

diff --git a/NetSharp/NetSharp/NetSharp.xml b/NetSharp/NetSharp/NetSharp.xml @@ -169,7 +169,7 @@ <member name="F:NetSharp.Sockets.Datagram.DatagramSocketClientOptions.PreallocatedTransmissionArgs"> <summary> The number of <see cref="T:System.Net.Sockets.SocketAsyncEventArgs" /> instances that should be preallocated for use in the - <see cref="M:NetSharp.Sockets.Datagram.DatagramSocketClient.SendToAsync(System.Net.EndPoint,System.Memory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)" /> and <see cref="M:NetSharp.Sockets.Datagram.DatagramSocketClient.ReceiveFromAsync(System.Net.EndPoint,System.Memory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)" /> methods. + <see cref="M:NetSharp.Sockets.Datagram.DatagramSocketClient.SendToAsync(System.Net.EndPoint,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)" /> and <see cref="M:NetSharp.Sockets.Datagram.DatagramSocketClient.ReceiveFromAsync(System.Net.EndPoint,System.Memory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)" /> methods. </summary> </member> <member name="M:NetSharp.Sockets.Datagram.DatagramSocketClientOptions.#ctor(System.UInt16)"> @@ -328,24 +328,50 @@ The cancellation token to observe during the operation. </param> </member> - <member name="T:NetSharp.Sockets.SocketClient.AsyncTransmissionToken"> + <member name="T:NetSharp.Sockets.SocketClient.AsyncReceiveToken"> <summary> - A state token for asynchronous network IO operations. + A state token for asynchronous incoming network IO operations. </summary> </member> - <member name="F:NetSharp.Sockets.SocketClient.AsyncTransmissionToken.CancellationToken"> + <member name="F:NetSharp.Sockets.SocketClient.AsyncReceiveToken.CancellationToken"> <summary> The <see cref="T:System.Threading.CancellationToken" /> associated with the network IO operation. </summary> </member> - <member name="F:NetSharp.Sockets.SocketClient.AsyncTransmissionToken.CompletionSource"> + <member name="F:NetSharp.Sockets.SocketClient.AsyncReceiveToken.CompletionSource"> <summary> The completion source which wraps the event-based APM, and provides an awaitable <see cref="T:System.Threading.Tasks.Task" />. </summary> </member> - <member name="M:NetSharp.Sockets.SocketClient.AsyncTransmissionToken.#ctor(System.Threading.Tasks.TaskCompletionSource{NetSharp.Utils.TransmissionResult}@,System.Net.Sockets.SocketAsyncEventArgs@,System.Threading.CancellationToken@)"> + <member name="M:NetSharp.Sockets.SocketClient.AsyncReceiveToken.#ctor(System.Threading.Tasks.TaskCompletionSource{NetSharp.Utils.TransmissionResult}@,System.Threading.CancellationToken@)"> <summary> - Constructs a new instance of the <see cref="T:NetSharp.Sockets.SocketClient.AsyncTransmissionToken" /> struct. + Constructs a new instance of the <see cref="T:NetSharp.Sockets.SocketClient.AsyncReceiveToken" /> struct. + </summary> + <param name="completionSource"> + The completion source to trigger when the IO operation completes. + </param> + <param name="cancellationToken"> + The cancellation token to observe during the operation. + </param> + </member> + <member name="T:NetSharp.Sockets.SocketClient.AsyncSendToken"> + <summary> + A state token for asynchronous outgoing network IO operations. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncSendToken.CancellationToken"> + <summary> + The <see cref="T:System.Threading.CancellationToken" /> associated with the network IO operation. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncSendToken.CompletionSource"> + <summary> + The completion source which wraps the event-based APM, and provides an awaitable <see cref="T:System.Threading.Tasks.Task" />. + </summary> + </member> + <member name="M:NetSharp.Sockets.SocketClient.AsyncSendToken.#ctor(System.Threading.Tasks.TaskCompletionSource{NetSharp.Utils.TransmissionResult}@,System.Byte[]@,System.Threading.CancellationToken@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Sockets.SocketClient.AsyncSendToken" /> struct. </summary> <param name="completionSource"> The completion source to trigger when the IO operation completes. @@ -564,7 +590,7 @@ <member name="F:NetSharp.Sockets.Stream.StreamSocketClientOptions.PreallocatedTransmissionArgs"> <summary> The number of <see cref="T:System.Net.Sockets.SocketAsyncEventArgs" /> instances that should be preallocated for use in the - <see cref="M:NetSharp.Sockets.Stream.StreamSocketClient.SendAsync(System.Memory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)" /> and <see cref="M:NetSharp.Sockets.Stream.StreamSocketClient.ReceiveAsync(System.Memory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)" /> methods. + <see cref="M:NetSharp.Sockets.Stream.StreamSocketClient.SendAsync(System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)" /> and <see cref="M:NetSharp.Sockets.Stream.StreamSocketClient.ReceiveAsync(System.Memory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)" /> methods. </summary> </member> <member name="M:NetSharp.Sockets.Stream.StreamSocketClientOptions.#ctor(System.UInt16)"> diff --git a/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs b/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs @@ -2,7 +2,6 @@ using NetSharp.Utils; using System; -using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Threading; @@ -84,13 +83,9 @@ namespace NetSharp.Sockets.Datagram private void CompleteReceiveFrom(SocketAsyncEventArgs args) { - AsyncTransmissionToken receiveToken = (AsyncTransmissionToken)args.UserToken; + AsyncReceiveToken receiveToken = (AsyncReceiveToken)args.UserToken; - if (receiveToken.CancellationToken.IsCancellationRequested) - { - Debug.WriteLine("ReceiveFrom completed after being cancelled!"); - return; - } + if (receiveToken.CancellationToken.IsCancellationRequested) return; switch (args.SocketError) { @@ -102,7 +97,6 @@ namespace NetSharp.Sockets.Datagram break; case SocketError.OperationAborted: - Debug.WriteLine("ReceiveFrom received operation aborted!"); break; default: @@ -116,7 +110,7 @@ namespace NetSharp.Sockets.Datagram private void CompleteSendTo(SocketAsyncEventArgs args) { - AsyncTransmissionToken sendToken = (AsyncTransmissionToken)args.UserToken; + AsyncSendToken sendToken = (AsyncSendToken)args.UserToken; if (sendToken.CancellationToken.IsCancellationRequested) return; @@ -138,6 +132,7 @@ namespace NetSharp.Sockets.Datagram break; } + BufferPool.Return(sendToken.RentedBuffer, true); TransmissionArgsPool.Return(args); } @@ -213,14 +208,20 @@ namespace NetSharp.Sockets.Datagram args.RemoteEndPoint = remoteEndPoint; args.SocketFlags = flags; - args.UserToken = new AsyncTransmissionToken(in tcs, in args, cancellationToken); + args.UserToken = new AsyncReceiveToken(in tcs, in cancellationToken); - // TODO find out why the fricc we leak memory - CancellationTokenRegistration cancellationRegistration = - cancellationToken.Register(CancelAsyncTransmissionCallback, args); + if (cancellationToken == default) + { + if (Connection.ReceiveFromAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); + } + else + { + // TODO find out why the fricc we leak memory + CancellationTokenRegistration cancellationRegistration = + cancellationToken.Register(CancelAsyncTransmissionCallback, args); - if (Connection.ReceiveFromAsync(args)) - return new ValueTask<TransmissionResult>( + if (Connection.ReceiveFromAsync(args)) + return new ValueTask<TransmissionResult>( tcs.Task.ContinueWith((task, state) => { ((CancellationTokenRegistration)state).Dispose(); @@ -229,7 +230,8 @@ namespace NetSharp.Sockets.Datagram }, cancellationRegistration, CancellationToken.None) ); - cancellationRegistration.Dispose(); + cancellationRegistration.Dispose(); + } TransmissionResult result = new TransmissionResult(in args); @@ -245,37 +247,48 @@ namespace NetSharp.Sockets.Datagram return new TransmissionResult(in sendBuffer, in sentBytes, in remoteEndPoint); } - public ValueTask<TransmissionResult> SendToAsync(EndPoint remoteEndPoint, Memory<byte> sendBuffer, + public ValueTask<TransmissionResult> SendToAsync(EndPoint remoteEndPoint, ReadOnlyMemory<byte> sendBuffer, SocketFlags flags = SocketFlags.None, CancellationToken cancellationToken = default) { TaskCompletionSource<TransmissionResult> tcs = new TaskCompletionSource<TransmissionResult>(); SocketAsyncEventArgs args = TransmissionArgsPool.Rent(); + byte[] transmissionBuffer = BufferPool.Rent(sendBuffer.Length); + + sendBuffer.CopyTo(transmissionBuffer); - args.SetBuffer(sendBuffer); + args.SetBuffer(transmissionBuffer); args.RemoteEndPoint = remoteEndPoint; args.SocketFlags = flags; - args.UserToken = new AsyncTransmissionToken(in tcs, in args, cancellationToken); + args.UserToken = new AsyncSendToken(in tcs, in transmissionBuffer, in cancellationToken); - // TODO find out why the fricc we leak memory - CancellationTokenRegistration cancellationRegistration = - cancellationToken.Register(CancelAsyncTransmissionCallback, args); + if (cancellationToken == default) + { + if (Connection.SendToAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); + } + else + { + // TODO find out why the fricc we leak memory + CancellationTokenRegistration cancellationRegistration = + cancellationToken.Register(CancelAsyncTransmissionCallback, args); - if (Connection.SendToAsync(args)) - return new ValueTask<TransmissionResult>( - tcs.Task.ContinueWith((task, state) => - { - ((CancellationTokenRegistration)state).Dispose(); + if (Connection.SendToAsync(args)) + return new ValueTask<TransmissionResult>( + tcs.Task.ContinueWith((task, state) => + { + ((CancellationTokenRegistration)state).Dispose(); - return task.Result; - }, cancellationRegistration, CancellationToken.None) - ); + return task.Result; + }, cancellationRegistration, CancellationToken.None) + ); - cancellationRegistration.Dispose(); + cancellationRegistration.Dispose(); + } TransmissionResult result = new TransmissionResult(in args); + BufferPool.Return(transmissionBuffer, true); TransmissionArgsPool.Return(args); return new ValueTask<TransmissionResult>(result); diff --git a/NetSharp/NetSharp/Sockets/SocketClient.cs b/NetSharp/NetSharp/Sockets/SocketClient.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics; -using NetSharp.Utils; +using NetSharp.Utils; using System.Net; using System.Net.Sockets; @@ -12,7 +10,7 @@ namespace NetSharp.Sockets /// <summary> /// Abstract base class for clients. /// </summary> - /// TODO implement cancellation of ReceiveAsync and ReceiveFromAsync methods. + /// TODO implement proper memory leak-free cancellation of network IO operations public abstract class SocketClient : SocketConnection { /// <summary> @@ -54,10 +52,11 @@ namespace NetSharp.Sockets { SocketAsyncEventArgs args = (SocketAsyncEventArgs)state; - AsyncTransmissionToken token = (AsyncTransmissionToken)args.UserToken; + AsyncSendToken token = (AsyncSendToken)args.UserToken; token.CompletionSource.SetResult(TransmissionResult.Timeout); + BufferPool.Return(token.RentedBuffer, true); DestroyTransmissionArgs(args); } @@ -90,7 +89,7 @@ namespace NetSharp.Sockets SocketAsyncEventArgs args = TransmissionArgsPool.Rent(); args.RemoteEndPoint = remoteEndPoint; - args.UserToken = new AsyncOperationToken(in tcs, CancellationToken.None); + args.UserToken = new AsyncOperationToken(in tcs, in cancellationToken); // TODO find out why the fricc we leak memory CancellationTokenRegistration cancellationRegistration = @@ -146,9 +145,9 @@ namespace NetSharp.Sockets } /// <summary> - /// A state token for asynchronous network IO operations. + /// A state token for asynchronous incoming network IO operations. /// </summary> - protected readonly struct AsyncTransmissionToken + protected readonly struct AsyncReceiveToken { /// <summary> /// The <see cref="System.Threading.CancellationToken" /> associated with the network IO operation. @@ -160,10 +159,42 @@ namespace NetSharp.Sockets /// </summary> public readonly TaskCompletionSource<TransmissionResult> CompletionSource; - public readonly SocketAsyncEventArgs TransmissionArgs; + /// <summary> + /// Constructs a new instance of the <see cref="AsyncReceiveToken" /> struct. + /// </summary> + /// <param name="completionSource"> + /// The completion source to trigger when the IO operation completes. + /// </param> + /// <param name="cancellationToken"> + /// The cancellation token to observe during the operation. + /// </param> + public AsyncReceiveToken(in TaskCompletionSource<TransmissionResult> completionSource, in CancellationToken cancellationToken) + { + CompletionSource = completionSource; + + CancellationToken = cancellationToken; + } + } + + /// <summary> + /// A state token for asynchronous outgoing network IO operations. + /// </summary> + protected readonly struct AsyncSendToken + { + /// <summary> + /// The <see cref="System.Threading.CancellationToken" /> associated with the network IO operation. + /// </summary> + public readonly CancellationToken CancellationToken; + + /// <summary> + /// The completion source which wraps the event-based APM, and provides an awaitable <see cref="Task" />. + /// </summary> + public readonly TaskCompletionSource<TransmissionResult> CompletionSource; + + public readonly byte[] RentedBuffer; /// <summary> - /// Constructs a new instance of the <see cref="AsyncTransmissionToken" /> struct. + /// Constructs a new instance of the <see cref="AsyncSendToken" /> struct. /// </summary> /// <param name="completionSource"> /// The completion source to trigger when the IO operation completes. @@ -171,11 +202,11 @@ namespace NetSharp.Sockets /// <param name="cancellationToken"> /// The cancellation token to observe during the operation. /// </param> - public AsyncTransmissionToken(in TaskCompletionSource<TransmissionResult> completionSource, in SocketAsyncEventArgs transmissionArgs, in CancellationToken cancellationToken) + public AsyncSendToken(in TaskCompletionSource<TransmissionResult> completionSource, in byte[] rentedBuffer, in CancellationToken cancellationToken) { CompletionSource = completionSource; - TransmissionArgs = transmissionArgs; + RentedBuffer = rentedBuffer; CancellationToken = cancellationToken; } diff --git a/NetSharp/NetSharp/Sockets/Stream/StreamSocketClient.cs b/NetSharp/NetSharp/Sockets/Stream/StreamSocketClient.cs @@ -2,7 +2,6 @@ using NetSharp.Utils; using System; -using System.Diagnostics; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -60,23 +59,22 @@ namespace NetSharp.Sockets.Stream { AsyncOperationToken connectToken = (AsyncOperationToken)args.UserToken; - if (!connectToken.CancellationToken.IsCancellationRequested) + if (connectToken.CancellationToken.IsCancellationRequested) return; + + switch (args.SocketError) { - switch (args.SocketError) - { - case SocketError.Success: - connectToken.CompletionSource.SetResult(true); + case SocketError.Success: + connectToken.CompletionSource.SetResult(true); - break; + break; - case SocketError.OperationAborted: - break; + case SocketError.OperationAborted: + break; - default: - connectToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + default: + connectToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); - break; - } + break; } TransmissionArgsPool.Return(args); @@ -86,23 +84,22 @@ namespace NetSharp.Sockets.Stream { AsyncOperationToken disconnectToken = (AsyncOperationToken)args.UserToken; - if (!disconnectToken.CancellationToken.IsCancellationRequested) + if (disconnectToken.CancellationToken.IsCancellationRequested) return; + + switch (args.SocketError) { - switch (args.SocketError) - { - case SocketError.Success: - disconnectToken.CompletionSource.SetResult(true); + case SocketError.Success: + disconnectToken.CompletionSource.SetResult(true); - break; + break; - case SocketError.OperationAborted: - break; + case SocketError.OperationAborted: + break; - default: - disconnectToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + default: + disconnectToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); - break; - } + break; } TransmissionArgsPool.Return(args); @@ -110,118 +107,105 @@ namespace NetSharp.Sockets.Stream private void CompleteReceive(SocketAsyncEventArgs args) { - AsyncTransmissionToken receiveToken = (AsyncTransmissionToken)args.UserToken; + AsyncReceiveToken receiveToken = (AsyncReceiveToken)args.UserToken; - if (!receiveToken.CancellationToken.IsCancellationRequested) - { - switch (args.SocketError) - { - case SocketError.Success: - Memory<byte> transmissionBuffer = args.MemoryBuffer; - int expectedBytes = transmissionBuffer.Length; - - if (args.BytesTransferred == expectedBytes) - { - // buffer was fully received - - TransmissionResult result = new TransmissionResult(in args); - - receiveToken.CompletionSource.SetResult(result); + if (receiveToken.CancellationToken.IsCancellationRequested) return; - TransmissionArgsPool.Return(args); - } - else if (expectedBytes > args.BytesTransferred && args.BytesTransferred > 0) - { - // receive the remaining parts of the buffer - - int receivedBytes = args.BytesTransferred; + switch (args.SocketError) + { + case SocketError.Success: + Memory<byte> transmissionBuffer = args.MemoryBuffer; + int expectedBytes = transmissionBuffer.Length; - args.SetBuffer(receivedBytes, expectedBytes - receivedBytes); + if (args.BytesTransferred == expectedBytes) + { + // buffer was fully received - Connection.ReceiveAsync(args); - } - else - { - // no bytes were received, remote socket is dead + TransmissionResult result = new TransmissionResult(in args); - receiveToken.CompletionSource.SetException(new SocketException((int)SocketError.HostDown)); + receiveToken.CompletionSource.SetResult(result); + } + else if (expectedBytes > args.BytesTransferred && args.BytesTransferred > 0) + { + // receive the remaining parts of the buffer - TransmissionArgsPool.Return(args); - } + int receivedBytes = args.BytesTransferred; - break; + args.SetBuffer(receivedBytes, expectedBytes - receivedBytes); - case SocketError.OperationAborted: - TransmissionArgsPool.Return(args); + Connection.ReceiveAsync(args); + return; + } + else + { + // no bytes were received, remote socket is dead - break; + receiveToken.CompletionSource.SetException(new SocketException((int)SocketError.HostDown)); + } - default: - receiveToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + break; - TransmissionArgsPool.Return(args); + case SocketError.OperationAborted: + break; - break; - } + default: + receiveToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + break; } + + TransmissionArgsPool.Return(args); } private void CompleteSend(SocketAsyncEventArgs args) { - AsyncTransmissionToken sendToken = (AsyncTransmissionToken)args.UserToken; + AsyncSendToken sendToken = (AsyncSendToken)args.UserToken; - if (!sendToken.CancellationToken.IsCancellationRequested) - { - switch (args.SocketError) - { - case SocketError.Success: - Memory<byte> transmissionBuffer = args.MemoryBuffer; - int remainingBytes = transmissionBuffer.Length; + if (sendToken.CancellationToken.IsCancellationRequested) return; - if (args.BytesTransferred == remainingBytes) - { - // buffer was fully sent - - TransmissionResult result = new TransmissionResult(in args); - - sendToken.CompletionSource.SetResult(result); - - TransmissionArgsPool.Return(args); - } - else if (remainingBytes > args.BytesTransferred && args.BytesTransferred > 0) - { - // send the remaining parts of the buffer - - int sentBytes = args.BytesTransferred; + switch (args.SocketError) + { + case SocketError.Success: + Memory<byte> transmissionBuffer = args.MemoryBuffer; + int remainingBytes = transmissionBuffer.Length; - args.SetBuffer(sentBytes, remainingBytes - sentBytes); + if (args.BytesTransferred == remainingBytes) + { + // buffer was fully sent - Connection.SendAsync(args); - } - else - { - // no bytes were sent, remote socket is dead + TransmissionResult result = new TransmissionResult(in args); - sendToken.CompletionSource.SetException(new SocketException((int)SocketError.HostDown)); + sendToken.CompletionSource.SetResult(result); + } + else if (remainingBytes > args.BytesTransferred && args.BytesTransferred > 0) + { + // send the remaining parts of the buffer - TransmissionArgsPool.Return(args); - } + int sentBytes = args.BytesTransferred; - break; + args.SetBuffer(sentBytes, remainingBytes - sentBytes); - case SocketError.OperationAborted: - TransmissionArgsPool.Return(args); + Connection.SendAsync(args); + return; + } + else + { + // no bytes were sent, remote socket is dead - break; + sendToken.CompletionSource.SetException(new SocketException((int)SocketError.HostDown)); + } - default: - sendToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + break; - TransmissionArgsPool.Return(args); + case SocketError.OperationAborted: + break; - break; - } + default: + sendToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + break; } + + BufferPool.Return(sendToken.RentedBuffer, true); + TransmissionArgsPool.Return(args); } /// <inheritdoc /> @@ -295,7 +279,7 @@ namespace NetSharp.Sockets.Stream SocketAsyncEventArgs args = TransmissionArgsPool.Rent(); args.DisconnectReuseSocket = allowSocketReuse; - args.UserToken = new AsyncOperationToken(in tcs, CancellationToken.None); + args.UserToken = new AsyncOperationToken(in tcs, in cancellationToken); // TODO find out why the fricc we leak memory CancellationTokenRegistration cancellationRegistration = @@ -340,23 +324,30 @@ namespace NetSharp.Sockets.Stream args.SetBuffer(receiveBuffer); args.SocketFlags = flags; - args.UserToken = new AsyncTransmissionToken(in tcs, in args, CancellationToken.None); + args.UserToken = new AsyncReceiveToken(in tcs, in cancellationToken); - // TODO find out why the fricc we leak memory - CancellationTokenRegistration cancellationRegistration = - cancellationToken.Register(CancelAsyncTransmissionCallback, args); + if (cancellationToken == default) + { + if (Connection.ReceiveAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); + } + else + { + // TODO find out why the fricc we leak memory + CancellationTokenRegistration cancellationRegistration = + cancellationToken.Register(CancelAsyncTransmissionCallback, args); - if (Connection.ReceiveAsync(args)) - return new ValueTask<TransmissionResult>( - tcs.Task.ContinueWith((task, state) => - { - ((CancellationTokenRegistration)state).Dispose(); + if (Connection.ReceiveAsync(args)) + return new ValueTask<TransmissionResult>( + tcs.Task.ContinueWith((task, state) => + { + ((CancellationTokenRegistration)state).Dispose(); - return task.Result; - }, cancellationRegistration, CancellationToken.None) - ); + return task.Result; + }, cancellationRegistration, CancellationToken.None) + ); - cancellationRegistration.Dispose(); + cancellationRegistration.Dispose(); + } TransmissionResult result = new TransmissionResult(in args); @@ -378,32 +369,42 @@ namespace NetSharp.Sockets.Stream return new TransmissionResult(in buffer, in sentBytes, Connection.RemoteEndPoint); } - public ValueTask<TransmissionResult> SendAsync(Memory<byte> sendBuffer, SocketFlags flags = SocketFlags.None, CancellationToken cancellationToken = default) + public ValueTask<TransmissionResult> SendAsync(ReadOnlyMemory<byte> sendBuffer, SocketFlags flags = SocketFlags.None, CancellationToken cancellationToken = default) { TaskCompletionSource<TransmissionResult> tcs = new TaskCompletionSource<TransmissionResult>(); SocketAsyncEventArgs args = TransmissionArgsPool.Rent(); + byte[] transmissionBuffer = BufferPool.Rent(sendBuffer.Length); - args.SetBuffer(sendBuffer); + sendBuffer.CopyTo(transmissionBuffer); + + args.SetBuffer(transmissionBuffer); args.SocketFlags = flags; - args.UserToken = new AsyncTransmissionToken(in tcs, in args, CancellationToken.None); + args.UserToken = new AsyncSendToken(in tcs, in transmissionBuffer, in cancellationToken); - // TODO find out why the fricc we leak memory - CancellationTokenRegistration cancellationRegistration = - cancellationToken.Register(CancelAsyncTransmissionCallback, args); + if (cancellationToken == default) + { + if (Connection.SendAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); + } + else + { + // TODO find out why the fricc we leak memory + CancellationTokenRegistration cancellationRegistration = + cancellationToken.Register(CancelAsyncTransmissionCallback, args); - if (Connection.SendToAsync(args)) - return new ValueTask<TransmissionResult>( - tcs.Task.ContinueWith((task, state) => - { - ((CancellationTokenRegistration)state).Dispose(); + if (Connection.SendAsync(args)) + return new ValueTask<TransmissionResult>( + tcs.Task.ContinueWith((task, state) => + { + ((CancellationTokenRegistration)state).Dispose(); - return task.Result; - }, cancellationRegistration, CancellationToken.None) - ); + return task.Result; + }, cancellationRegistration, CancellationToken.None) + ); - cancellationRegistration.Dispose(); + cancellationRegistration.Dispose(); + } TransmissionResult result = new TransmissionResult(in args); diff --git a/NetSharp/NetSharpExamples/Benchmarks/UdpSocketClientAsyncBenchmark.cs b/NetSharp/NetSharpExamples/Benchmarks/UdpSocketClientAsyncBenchmark.cs @@ -35,9 +35,9 @@ namespace NetSharpExamples.Benchmarks while (!cancellationToken.IsCancellationRequested) { - server.ReceiveFrom(transmissionBuffer, ref remoteEndPoint); + int received = server.ReceiveFrom(transmissionBuffer, ref remoteEndPoint); - server.SendTo(transmissionBuffer, remoteEndPoint); + int sent = server.SendTo(transmissionBuffer, remoteEndPoint); } server.Close(); @@ -55,7 +55,7 @@ namespace NetSharpExamples.Benchmarks Console.WriteLine($"{PacketCount} packets will be sent per client. This could take a long time (maybe more than a minute)!"); } - using CancellationTokenSource serverCts = new CancellationTokenSource(); serverCts.Cancel(); + using CancellationTokenSource serverCts = new CancellationTokenSource(); Task serverTask = Task.Factory.StartNew(state => ServerTask((CancellationToken)state), serverCts.Token, TaskCreationOptions.LongRunning); BenchmarkHelper benchmarkHelper = new BenchmarkHelper();