diff --git a/api_list.include.md b/api_list.include.md index 2abfa41b..6f9d392f 100644 --- a/api_list.include.md +++ b/api_list.include.md @@ -964,6 +964,14 @@ * `bool TryPop(T)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.stack-1.trypop?view=net-11.0) +#### Socket + + * `ValueTask ConnectAsync(EndPoint, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken)) + * `ValueTask DisconnectAsync(bool, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken)) + * `ValueTask ReceiveFromAsync(Memory, SocketFlags, EndPoint, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken)) + * `ValueTask SendToAsync(ReadOnlyMemory, SocketFlags, EndPoint, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken)) + + #### Stopwatch * `TimeSpan GetElapsedTime(long, long)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch.getelapsedtime?view=net-11.0#system-diagnostics-stopwatch-getelapsedtime(system-int64-system-int64)) @@ -1340,4 +1348,3 @@ #### TaskCompletionSource #### UnreachableException - diff --git a/readme.md b/readme.md index 225a7388..07037aec 100644 --- a/readme.md +++ b/readme.md @@ -113,7 +113,7 @@ This project uses features from the current stable SDK and C# language. As such | net5.0 | 9.5KB | 208.0KB | +198.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | | net6.0 | 10.0KB | 152.0KB | +142.0KB | +10.0KB | +7.0KB | +512bytes | +4.0KB | | net7.0 | 10.0KB | 117.5KB | +107.5KB | +9.0KB | +5.5KB | +512bytes | +4.0KB | -| net8.0 | 9.5KB | 89.0KB | +79.5KB | +8.5KB | +512bytes | +1.0KB | +3.5KB | +| net8.0 | 9.5KB | 89.0KB | +79.5KB | +9.0KB | +512bytes | +1.0KB | +3.5KB | | net9.0 | 9.5KB | 47.0KB | +37.5KB | +9.0KB | | +1.0KB | +3.5KB | | net10.0 | 10.0KB | 23.5KB | +13.5KB | +9.0KB | | +512bytes | +3.5KB | | net11.0 | 10.0KB | 18.5KB | +8.5KB | +9.0KB | | +512bytes | +3.5KB | @@ -124,23 +124,23 @@ This project uses features from the current stable SDK and C# language. As such | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| | netstandard2.0 | 8.0KB | 436.4KB | +428.4KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| netstandard2.1 | 8.5KB | 363.0KB | +354.5KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | -| net461 | 8.5KB | 434.4KB | +425.9KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| net462 | 7.0KB | 437.9KB | +430.9KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| netstandard2.1 | 8.5KB | 362.9KB | +354.4KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net461 | 8.5KB | 434.3KB | +425.8KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| net462 | 7.0KB | 437.8KB | +430.8KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | | net47 | 7.0KB | 437.6KB | +430.6KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | | net471 | 8.5KB | 437.6KB | +429.1KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | | net472 | 8.5KB | 435.0KB | +426.5KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | | net48 | 8.5KB | 435.0KB | +426.5KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | | net481 | 8.5KB | 435.0KB | +426.5KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | | netcoreapp2.0 | 9.0KB | 402.3KB | +393.3KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.1 | 9.0KB | 370.2KB | +361.2KB | +16.7KB | +8.7KB | +14.4KB | +19.4KB | -| netcoreapp2.2 | 9.0KB | 370.2KB | +361.2KB | +16.7KB | +8.7KB | +14.4KB | +19.4KB | -| netcoreapp3.0 | 9.5KB | 351.9KB | +342.4KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| netcoreapp2.1 | 9.0KB | 370.1KB | +361.1KB | +16.7KB | +8.7KB | +14.4KB | +19.4KB | +| netcoreapp2.2 | 9.0KB | 370.1KB | +361.1KB | +16.7KB | +8.7KB | +14.4KB | +19.4KB | +| netcoreapp3.0 | 9.5KB | 351.8KB | +342.3KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | | netcoreapp3.1 | 9.5KB | 350.3KB | +340.8KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| net5.0 | 9.5KB | 297.2KB | +287.8KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| net6.0 | 10.0KB | 223.5KB | +213.5KB | +17.7KB | +8.7KB | +1.1KB | +4.7KB | +| net5.0 | 9.5KB | 297.2KB | +287.7KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| net6.0 | 10.0KB | 223.4KB | +213.4KB | +17.7KB | +8.7KB | +1.1KB | +4.7KB | | net7.0 | 10.0KB | 170.2KB | +160.2KB | +16.6KB | +6.9KB | +1.1KB | +4.7KB | -| net8.0 | 9.5KB | 126.8KB | +117.3KB | +16.0KB | +811bytes | +1.6KB | +4.2KB | +| net8.0 | 9.5KB | 126.8KB | +117.3KB | +16.5KB | +811bytes | +1.6KB | +4.2KB | | net9.0 | 9.5KB | 67.8KB | +58.3KB | +16.5KB | | +1.6KB | +4.2KB | | net10.0 | 10.0KB | 35.8KB | +25.8KB | +16.5KB | | +1.1KB | +4.2KB | | net11.0 | 10.0KB | 27.4KB | +17.4KB | +16.5KB | | +1.1KB | +4.2KB | @@ -1495,6 +1495,14 @@ The class `Polyfill` includes the following extension methods: * `bool TryPop(T)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.stack-1.trypop?view=net-11.0) +#### Socket + + * `ValueTask ConnectAsync(EndPoint, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken)) + * `ValueTask DisconnectAsync(bool, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken)) + * `ValueTask ReceiveFromAsync(Memory, SocketFlags, EndPoint, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken)) + * `ValueTask SendToAsync(ReadOnlyMemory, SocketFlags, EndPoint, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken)) + + #### Stopwatch * `TimeSpan GetElapsedTime(long, long)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch.getelapsedtime?view=net-11.0#system-diagnostics-stopwatch-getelapsedtime(system-int64-system-int64)) @@ -1870,8 +1878,7 @@ The class `Polyfill` includes the following extension methods: #### TaskCompletionSource -#### UnreachableException - +#### UnreachableException ## References diff --git a/src/Consume/Consume.cs b/src/Consume/Consume.cs index d74cba8e..9d43571e 100644 --- a/src/Consume/Consume.cs +++ b/src/Consume/Consume.cs @@ -879,6 +879,19 @@ async Task UdpClient_Methods() await connectedClient.SendAsync(data, CancellationToken.None); await client.SendAsync(data, new(IPAddress.Loopback, 12345), CancellationToken.None); await client.SendAsync(data, "localhost", 12345, CancellationToken.None); +#endif + } + + async Task Socket_Methods() + { + using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + await socket.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 12345), CancellationToken.None); + await socket.DisconnectAsync(false, CancellationToken.None); +#if FeatureMemory + Memory receiveBuffer = new byte[3]; + ReadOnlyMemory sendBuffer = new byte[] {1, 2, 3}; + await socket.ReceiveFromAsync(receiveBuffer, SocketFlags.None, new IPEndPoint(IPAddress.Any, 0), CancellationToken.None); + await socket.SendToAsync(sendBuffer, SocketFlags.None, new IPEndPoint(IPAddress.Loopback, 12345), CancellationToken.None); #endif } #endif @@ -1410,6 +1423,22 @@ void Task_Methods() Task.FromResult(0).ConfigureAwait(ConfigureAwaitOptions.ForceYielding); } + async Task Task_ConfigureAwait_Options_Methods() + { + var action = () => { }; + var task = new Task(action); + await task.ConfigureAwait(ConfigureAwaitOptions.None); + await task.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext); + await task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); + await task.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); + + var func = () => 0; + var genericTask = new Task(func); + await genericTask.ConfigureAwait(ConfigureAwaitOptions.None); + await genericTask.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext); + await genericTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); + } + #if FeatureMemory void Task_WhenAllAny_Span_Methods() { diff --git a/src/Polyfill/Polyfill_Socket.cs b/src/Polyfill/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Polyfill/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Polyfill/SocketReceiveFromResult.cs b/src/Polyfill/SocketReceiveFromResult.cs new file mode 100644 index 00000000..da540685 --- /dev/null +++ b/src/Polyfill/SocketReceiveFromResult.cs @@ -0,0 +1,36 @@ +#if NET461 || NET462 || NET47 || WINDOWS_UWP +#nullable disable + +namespace System.Net.Sockets; + +using System.Net; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +/// +/// The result of a receive-from asynchronous socket operation. +/// +//Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socketreceivefromresult?view=net-11.0 +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +struct SocketReceiveFromResult +{ + /// + /// The number of bytes received. + /// + public int ReceivedBytes; + + /// + /// The source endpoint. + /// + public EndPoint RemoteEndPoint; +} +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Net.Sockets.SocketReceiveFromResult))] +#endif diff --git a/src/Split/net461/Polyfill_Socket.cs b/src/Split/net461/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/net461/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/net461/SocketReceiveFromResult.cs b/src/Split/net461/SocketReceiveFromResult.cs new file mode 100644 index 00000000..4281773f --- /dev/null +++ b/src/Split/net461/SocketReceiveFromResult.cs @@ -0,0 +1,19 @@ +// +#nullable disable +namespace System.Net.Sockets; +using System.Net; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +struct SocketReceiveFromResult +{ + public int ReceivedBytes; + public EndPoint RemoteEndPoint; +} diff --git a/src/Split/net462/Polyfill_Socket.cs b/src/Split/net462/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/net462/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/net462/SocketReceiveFromResult.cs b/src/Split/net462/SocketReceiveFromResult.cs new file mode 100644 index 00000000..4281773f --- /dev/null +++ b/src/Split/net462/SocketReceiveFromResult.cs @@ -0,0 +1,19 @@ +// +#nullable disable +namespace System.Net.Sockets; +using System.Net; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +struct SocketReceiveFromResult +{ + public int ReceivedBytes; + public EndPoint RemoteEndPoint; +} diff --git a/src/Split/net47/Polyfill_Socket.cs b/src/Split/net47/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/net47/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/net47/SocketReceiveFromResult.cs b/src/Split/net47/SocketReceiveFromResult.cs new file mode 100644 index 00000000..4281773f --- /dev/null +++ b/src/Split/net47/SocketReceiveFromResult.cs @@ -0,0 +1,19 @@ +// +#nullable disable +namespace System.Net.Sockets; +using System.Net; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +struct SocketReceiveFromResult +{ + public int ReceivedBytes; + public EndPoint RemoteEndPoint; +} diff --git a/src/Split/net471/Polyfill_Socket.cs b/src/Split/net471/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/net471/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/net472/Polyfill_Socket.cs b/src/Split/net472/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/net472/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/net48/Polyfill_Socket.cs b/src/Split/net48/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/net48/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/net481/Polyfill_Socket.cs b/src/Split/net481/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/net481/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/net5.0/Polyfill_Socket.cs b/src/Split/net5.0/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/net5.0/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/netcoreapp2.0/Polyfill_Socket.cs b/src/Split/netcoreapp2.0/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/netcoreapp2.0/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/netcoreapp2.1/Polyfill_Socket.cs b/src/Split/netcoreapp2.1/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/netcoreapp2.1/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/netcoreapp2.2/Polyfill_Socket.cs b/src/Split/netcoreapp2.2/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/netcoreapp2.2/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/netcoreapp3.0/Polyfill_Socket.cs b/src/Split/netcoreapp3.0/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/netcoreapp3.0/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/netcoreapp3.1/Polyfill_Socket.cs b/src/Split/netcoreapp3.1/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/netcoreapp3.1/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/netstandard2.0/Polyfill_Socket.cs b/src/Split/netstandard2.0/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/netstandard2.0/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/netstandard2.1/Polyfill_Socket.cs b/src/Split/netstandard2.1/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/netstandard2.1/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/uap10.0/Polyfill_Socket.cs b/src/Split/uap10.0/Polyfill_Socket.cs new file mode 100644 index 00000000..3418eeab --- /dev/null +++ b/src/Split/uap10.0/Polyfill_Socket.cs @@ -0,0 +1,465 @@ +#pragma warning disable +#if FeatureValueTask + +namespace Polyfills; + +using System; +#if FeatureMemory +using System.Buffers; +using System.Runtime.InteropServices; +#endif +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ +#if !NET6_0_OR_GREATER + +#if !NET5_0_OR_GREATER + + /// + /// Establishes a connection to a remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connectasync?view=net-11.0#system-net-sockets-socket-connectasync(system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ConnectAsync( + this Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ConnectWithCancellationAsync(target, remoteEP, cancellationToken)); + } + + static Task ConnectWithCancellationAsync( + Socket target, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ConnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif + +#if FeatureMemory + + /// + /// Receives data and returns the endpoint of the sending host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivefromasync?view=net-11.0#system-net-sockets-socket-receivefromasync(system-memory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask ReceiveFromAsync( + this Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken = default) + { + if (remoteEndPoint is null) + { + throw new ArgumentNullException(nameof(remoteEndPoint)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(ReceiveFromWithCancellationAsync(target, buffer, socketFlags, remoteEndPoint, cancellationToken)); + } + + static Task ReceiveFromWithCancellationAsync( + Socket target, + Memory buffer, + SocketFlags socketFlags, + EndPoint remoteEndPoint, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEndPoint, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + if (eventArgs.SocketError == SocketError.Success) + { + if (rentedBuffer is not null) + { + rentedBuffer.AsMemory(0, eventArgs.BytesTransferred).CopyTo(buffer); + } + + var result = new SocketReceiveFromResult + { + ReceivedBytes = eventArgs.BytesTransferred, + RemoteEndPoint = eventArgs.RemoteEndPoint! + }; + + Cleanup(eventArgs); + taskCompletionSource.TrySetResult(result); + return; + } + + Cleanup(eventArgs); + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.ReceiveFromAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + /// + /// Sends data to the specified remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.sendtoasync?view=net-11.0#system-net-sockets-socket-sendtoasync(system-readonlymemory((system-byte))-system-net-sockets-socketflags-system-net-endpoint-system-threading-cancellationtoken) + public static ValueTask SendToAsync( + this Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken = default) + { + if (remoteEP is null) + { + throw new ArgumentNullException(nameof(remoteEP)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(SendToWithCancellationAsync(target, buffer, socketFlags, remoteEP, cancellationToken)); + } + + static Task SendToWithCancellationAsync( + Socket target, + ReadOnlyMemory buffer, + SocketFlags socketFlags, + EndPoint remoteEP, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + RemoteEndPoint = remoteEP, + SocketFlags = socketFlags + }; + CancellationTokenRegistration registration = default; + var segment = GetArraySegment(buffer, out var rentedBuffer); + args.SetBuffer(segment.Array, segment.Offset, segment.Count); + + void Cleanup(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + if (rentedBuffer is not null) + { + ArrayPool.Shared.Return(rentedBuffer); + rentedBuffer = null; + } + } + + void Complete(SocketAsyncEventArgs eventArgs) + { + Cleanup(eventArgs); + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(eventArgs.BytesTransferred); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.SendToAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + Cleanup(args); + return Task.FromCanceled(cancellationToken); + } + catch + { + Cleanup(args); + throw; + } + + return taskCompletionSource.Task; + } + + static ArraySegment GetArraySegment( + Memory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray((ReadOnlyMemory) buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + + static ArraySegment GetArraySegment( + ReadOnlyMemory buffer, + out byte[]? rentedBuffer) + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + rentedBuffer = null; + return segment; + } + + rentedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(rentedBuffer.AsSpan()); + return new ArraySegment(rentedBuffer, 0, buffer.Length); + } + +#endif + + /// + /// Disconnects a connected socket from the remote host. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.disconnectasync?view=net-11.0#system-net-sockets-socket-disconnectasync(system-boolean-system-threading-cancellationtoken) + public static ValueTask DisconnectAsync( + this Socket target, + bool reuseSocket, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } + + return new ValueTask(DisconnectWithCancellationAsync(target, reuseSocket, cancellationToken)); + } + + static Task DisconnectWithCancellationAsync( + Socket target, + bool reuseSocket, + CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var args = new SocketAsyncEventArgs + { + DisconnectReuseSocket = reuseSocket + }; + CancellationTokenRegistration registration = default; + + void Complete(SocketAsyncEventArgs eventArgs) + { + registration.Dispose(); + eventArgs.Completed -= OnCompleted; + eventArgs.Dispose(); + + if (eventArgs.SocketError == SocketError.Success) + { + taskCompletionSource.TrySetResult(true); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + taskCompletionSource.TrySetCanceled(cancellationToken); + return; + } + + taskCompletionSource.TrySetException(new SocketException((int) eventArgs.SocketError)); + } + + void OnCompleted(object? _, SocketAsyncEventArgs eventArgs) => + Complete(eventArgs); + + args.Completed += OnCompleted; + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.Register(() => target.Close()); + } + + try + { + if (!target.DisconnectAsync(args)) + { + Complete(args); + } + } + catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch (SocketException) when (cancellationToken.IsCancellationRequested) + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + return Task.FromCanceled(cancellationToken); + } + catch + { + registration.Dispose(); + args.Completed -= OnCompleted; + args.Dispose(); + throw; + } + + return taskCompletionSource.Task; + } + +#endif +} +#endif diff --git a/src/Split/uap10.0/SocketReceiveFromResult.cs b/src/Split/uap10.0/SocketReceiveFromResult.cs new file mode 100644 index 00000000..4281773f --- /dev/null +++ b/src/Split/uap10.0/SocketReceiveFromResult.cs @@ -0,0 +1,19 @@ +// +#nullable disable +namespace System.Net.Sockets; +using System.Net; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +struct SocketReceiveFromResult +{ + public int ReceivedBytes; + public EndPoint RemoteEndPoint; +} diff --git a/src/Tests/PolyfillTests_Socket.cs b/src/Tests/PolyfillTests_Socket.cs new file mode 100644 index 00000000..59e5ef9c --- /dev/null +++ b/src/Tests/PolyfillTests_Socket.cs @@ -0,0 +1,63 @@ +using System.Net; +using System.Net.Sockets; + +partial class PolyfillTests +{ +#if FeatureValueTask + [Test] + public async Task SocketConnectAsync_EndPoint_Cancelled_ThrowsOperationCanceledException() + { + // Arrange + var cancel = new Cancel(true); + using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + var endpoint = new IPEndPoint(IPAddress.Loopback, 12345); + + // Act & Assert + await Assert.ThrowsAsync( + async () => await socket.ConnectAsync(endpoint, cancel)); + } + + [Test] + public async Task SocketDisconnectAsync_Cancelled_ThrowsOperationCanceledException() + { + // Arrange + var cancel = new Cancel(true); + using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + // Act & Assert + await Assert.ThrowsAsync( + async () => await socket.DisconnectAsync(false, cancel)); + } + +#if FeatureMemory + [Test] + public async Task SocketReceiveFromAsync_Memory_Cancelled_ThrowsOperationCanceledException() + { + // Arrange + var cancel = new Cancel(true); + using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + Memory buffer = new byte[3]; + EndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); + + // Act & Assert + await Assert.ThrowsAsync( + async () => await socket.ReceiveFromAsync(buffer, SocketFlags.None, endpoint, cancel)); + } + + [Test] + public async Task SocketSendToAsync_ReadOnlyMemory_Cancelled_ThrowsOperationCanceledException() + { + // Arrange + var cancel = new Cancel(true); + using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + ReadOnlyMemory buffer = new byte[] { 1, 2, 3 }; + EndPoint endpoint = new IPEndPoint(IPAddress.Loopback, 12345); + + // Act & Assert + await Assert.ThrowsAsync( + async () => await socket.SendToAsync(buffer, SocketFlags.None, endpoint, cancel)); + } +#endif +#endif +}