From 3e6de7ea5a891c66ff0f98050bf0fb6381871ac1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?G=C3=BCnther=20Foidl?= Date: Thu, 6 May 2021 17:44:25 +0200 Subject: [PATCH] Socket.SendFileAsync based on SendPacketsAsync (#52208) * Socket.SendFileAsync layered on top of SendPacketsAsync * Tests * Cleanup * Set SendPacketsFlags * Check if the socket is connection orientated Cf. https://github.com/dotnet/runtime/pull/52208#discussion_r625922176 * Try to re-use the SendPacketsElement-array Cf. https://github.com/dotnet/runtime/pull/52208#discussion_r625346014 * Fixed test Cf. https://github.com/dotnet/runtime/pull/52208#discussion_r625316709 * Update src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs --- .../System.Net.Sockets/ref/System.Net.Sockets.cs | 2 + .../src/System.Net.Sockets.csproj | 1 - .../src/System/Net/Sockets/SendPacketsElement.cs | 2 +- .../src/System/Net/Sockets/Socket.Tasks.cs | 109 +++++++++++++++++++++ .../src/System/Net/Sockets/Socket.Unix.cs | 56 ----------- .../src/System/Net/Sockets/Socket.Windows.cs | 53 ---------- .../src/System/Net/Sockets/Socket.cs | 4 +- .../src/System/Net/Sockets/SocketPal.Unix.cs | 3 - .../src/System/Net/Sockets/SocketPal.Windows.cs | 24 ----- .../Net/Sockets/TransmitFileAsyncResult.Windows.cs | 63 ------------ .../tests/FunctionalTests/SendFile.cs | 23 +++-- .../tests/FunctionalTests/SocketTestHelper.cs | 12 ++- 12 files changed, 137 insertions(+), 215 deletions(-) delete mode 100644 src/libraries/System.Net.Sockets/src/System/Net/Sockets/TransmitFileAsyncResult.Windows.cs diff --git a/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs b/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs index b96ce3a..68b963f 100644 --- a/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs +++ b/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs @@ -412,6 +412,8 @@ namespace System.Net.Sockets public void SendFile(string? fileName) { } public void SendFile(string? fileName, byte[]? preBuffer, byte[]? postBuffer, System.Net.Sockets.TransmitFileOptions flags) { } public void SendFile(string? fileName, System.ReadOnlySpan preBuffer, System.ReadOnlySpan postBuffer, System.Net.Sockets.TransmitFileOptions flags) { } + public System.Threading.Tasks.ValueTask SendFileAsync(string? fileName, System.ReadOnlyMemory preBuffer, System.ReadOnlyMemory postBuffer, System.Net.Sockets.TransmitFileOptions flags, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.ValueTask SendFileAsync(string? fileName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public bool SendPacketsAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; } public int SendTo(byte[] buffer, int offset, int size, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; } public int SendTo(byte[] buffer, int size, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; } diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj index c576e1b..071580a 100644 --- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -95,7 +95,6 @@ - diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SendPacketsElement.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SendPacketsElement.cs index 028b1b4..f528bf0 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SendPacketsElement.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SendPacketsElement.cs @@ -102,7 +102,7 @@ namespace System.Net.Sockets throw new ArgumentOutOfRangeException(nameof(count)); } - Initialize(null, null, buffer, new ReadOnlyMemory(buffer, offset, count), offset, count, endOfPacket); + Initialize(null, null, buffer, buffer.AsMemory(offset, count), offset, count, endOfPacket); } public SendPacketsElement(ReadOnlyMemory buffer) : diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs index ac604eb..ae67f70 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs @@ -555,6 +555,95 @@ namespace System.Net.Sockets return saea.SendToAsync(this, cancellationToken); } + /// + /// Sends the file to a connected object. + /// + /// A that contains the path and name of the file to be sent. This parameter can be . + /// A cancellation token that can be used to cancel the asynchronous operation. + /// The object has been closed. + /// The object is not connected to a remote host. + /// The file was not found. + /// An error occurred when attempting to access the socket. + public ValueTask SendFileAsync(string? fileName, CancellationToken cancellationToken = default) + { + return SendFileAsync(fileName, default, default, TransmitFileOptions.UseDefaultWorkerThread, cancellationToken); + } + + /// + /// Sends the file and buffers of data to a connected object + /// using the specified value. + /// + /// A that contains the path and name of the file to be sent. This parameter can be . + /// A array that contains data to be sent before the file is sent. This parameter can be . + /// A array that contains data to be sent after the file is sent. This parameter can be . + /// One or more of values. + /// A cancellation token that can be used to cancel the asynchronous operation. + /// The object has been closed. + /// The object is not connected to a remote host. + /// The file was not found. + /// An error occurred when attempting to access the socket. + public ValueTask SendFileAsync(string? fileName, ReadOnlyMemory preBuffer, ReadOnlyMemory postBuffer, TransmitFileOptions flags, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return ValueTask.FromCanceled(cancellationToken); + } + + if (!IsConnectionOriented) + { + var soex = new SocketException((int)SocketError.NotConnected); + return ValueTask.FromException(soex); + } + + int packetsCount = 0; + + if (fileName is not null) + { + packetsCount++; + } + + if (!preBuffer.IsEmpty) + { + packetsCount++; + } + + if (!postBuffer.IsEmpty) + { + packetsCount++; + } + + AwaitableSocketAsyncEventArgs saea = + Interlocked.Exchange(ref _singleBufferSendEventArgs, null) ?? + new AwaitableSocketAsyncEventArgs(this, isReceiveForCaching: false); + + SendPacketsElement[] sendPacketsElements = saea.SendPacketsElements?.Length == packetsCount + ? saea.SendPacketsElements + : new SendPacketsElement[packetsCount]; + + int index = 0; + if (!preBuffer.IsEmpty) + { + sendPacketsElements[index++] = new SendPacketsElement(preBuffer, endOfPacket: index == packetsCount); + } + + if (fileName is not null) + { + sendPacketsElements[index++] = new SendPacketsElement(fileName, 0, 0, endOfPacket: index == packetsCount); + } + + if (!postBuffer.IsEmpty) + { + sendPacketsElements[index++] = new SendPacketsElement(postBuffer, endOfPacket: index == packetsCount); + } + + Debug.Assert(index == packetsCount); + + saea.SendPacketsFlags = flags; + saea.SendPacketsElements = sendPacketsElements; + saea.WrapExceptionsForNetworkStream = false; + return saea.SendPacketsAsync(this, cancellationToken); + } + private static void ValidateBufferArguments(byte[] buffer, int offset, int size) { if (buffer == null) @@ -1005,6 +1094,26 @@ namespace System.Net.Sockets ValueTask.FromException(CreateException(error)); } + public ValueTask SendPacketsAsync(Socket socket, CancellationToken cancellationToken) + { + Debug.Assert(Volatile.Read(ref _continuation) == null, "Expected null continuation to indicate reserved for use"); + + // TODO: Support cancellation by passing cancellationToken down through SendPacketsAsync, etc. + if (socket.SendPacketsAsync(this)) + { + _cancellationToken = cancellationToken; + return new ValueTask(this, _token); + } + + SocketError error = SocketError; + + Release(); + + return error == SocketError.Success ? + default : + ValueTask.FromException(CreateException(error)); + } + public ValueTask SendToAsync(Socket socket, CancellationToken cancellationToken) { Debug.Assert(Volatile.Read(ref _continuation) == null, "Expected null continuation to indicate reserved for use"); diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs index a46a305..594a09f 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs @@ -226,61 +226,5 @@ namespace System.Net.Sockets Send(postBuffer); } } - - private async Task SendFileInternalAsync(FileStream? fileStream, byte[]? preBuffer, byte[]? postBuffer) - { - SocketError errorCode = SocketError.Success; - using (fileStream) - { - // Send the preBuffer, if any - // This will throw on error - if (preBuffer != null && preBuffer.Length > 0) - { - // Using "this." makes the extension method kick in - await this.SendAsync(new ArraySegment(preBuffer), SocketFlags.None).ConfigureAwait(false); - } - - // Send the file, if any - if (fileStream != null) - { - var tcs = new TaskCompletionSource(); - errorCode = SocketPal.SendFileAsync(_handle, fileStream, (_, socketError) => tcs.SetResult(socketError)); - if (errorCode == SocketError.IOPending) - { - errorCode = await tcs.Task.ConfigureAwait(false); - } - } - } - - if (errorCode != SocketError.Success) - { - UpdateSendSocketErrorForDisposed(ref errorCode); - UpdateStatusAfterSocketErrorAndThrowException(errorCode); - } - - // Send the postBuffer, if any - // This will throw on error - if (postBuffer != null && postBuffer.Length > 0) - { - // Using "this." makes the extension method kick in - await this.SendAsync(new ArraySegment(postBuffer), SocketFlags.None).ConfigureAwait(false); - } - } - - private IAsyncResult BeginSendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postBuffer, TransmitFileOptions flags, AsyncCallback? callback, object? state) - { - CheckTransmitFileOptions(flags); - - // Open the file, if any - // Open it before we send the preBuffer so that any exception happens first - FileStream? fileStream = OpenFile(fileName); - - return TaskToApm.Begin(SendFileInternalAsync(fileStream, preBuffer, postBuffer), callback, state); - } - - private void EndSendFileInternal(IAsyncResult asyncResult) - { - TaskToApm.End(asyncResult); - } } } diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs index 417386e..6f4e2ed 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs @@ -400,59 +400,6 @@ namespace System.Net.Sockets } } - private IAsyncResult BeginSendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postBuffer, TransmitFileOptions flags, AsyncCallback? callback, object? state) - { - FileStream? fileStream = OpenFile(fileName); - - TransmitFileAsyncResult asyncResult = new TransmitFileAsyncResult(this, state, callback); - asyncResult.StartPostingAsyncOp(false); - - SocketError errorCode = SocketPal.SendFileAsync(_handle, fileStream, preBuffer, postBuffer, flags, asyncResult); - - // Check for synchronous exception - if (!CheckErrorAndUpdateStatus(errorCode)) - { - UpdateSendSocketErrorForDisposed(ref errorCode); - throw new SocketException((int)errorCode); - } - - asyncResult.FinishPostingAsyncOp(); - - return asyncResult; - } - - private void EndSendFileInternal(IAsyncResult asyncResult) - { - TransmitFileAsyncResult? castedAsyncResult = asyncResult as TransmitFileAsyncResult; - if (castedAsyncResult == null || castedAsyncResult.AsyncObject != this) - { - throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult)); - } - - if (castedAsyncResult.EndCalled) - { - throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, "EndSendFile")); - } - - castedAsyncResult.InternalWaitForCompletion(); - castedAsyncResult.EndCalled = true; - - // If the user passed the Disconnect and/or ReuseSocket flags, then TransmitFile disconnected the socket. - // Update our state to reflect this. - if (castedAsyncResult.DoDisconnect) - { - SetToDisconnected(); - _remoteEndPoint = null; - } - - SocketError errorCode = (SocketError)castedAsyncResult.ErrorCode; - if (errorCode != SocketError.Success) - { - UpdateSendSocketErrorForDisposed(ref errorCode); - UpdateStatusAfterSocketErrorAndThrowException(errorCode); - } - } - internal ThreadPoolBoundHandle GetOrAllocateThreadPoolBoundHandle() => _handle.GetThreadPoolBoundHandle() ?? GetOrAllocateThreadPoolBoundHandleSlow(); diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs index 796bd6e..b7cddab 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs @@ -2216,7 +2216,7 @@ namespace System.Net.Sockets if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"::DoBeginSendFile() SRC:{LocalEndPoint} DST:{RemoteEndPoint} fileName:{fileName}"); - return BeginSendFileInternal(fileName, preBuffer, postBuffer, flags, callback, state); + return TaskToApm.Begin(SendFileAsync(fileName, preBuffer, postBuffer, flags).AsTask(), callback, state); } public void EndSendFile(IAsyncResult asyncResult) @@ -2228,7 +2228,7 @@ namespace System.Net.Sockets throw new ArgumentNullException(nameof(asyncResult)); } - EndSendFileInternal(asyncResult); + TaskToApm.End(asyncResult); } public IAsyncResult BeginSendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP, AsyncCallback? callback, object? state) diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs index 46c9ad4..5b44cf4 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs @@ -1881,9 +1881,6 @@ namespace System.Net.Sockets return GetSocketErrorForErrorCode(err); } - public static SocketError SendFileAsync(SafeSocketHandle handle, FileStream fileStream, Action callback) => - SendFileAsync(handle, fileStream, 0, fileStream.Length, callback); - private static SocketError SendFileAsync(SafeSocketHandle handle, FileStream fileStream, long offset, long count, Action callback) { long bytesSent; diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs index 8d10920..6e59eda 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs @@ -1074,30 +1074,6 @@ namespace System.Net.Sockets } } - public static unsafe SocketError SendFileAsync(SafeSocketHandle handle, FileStream? fileStream, byte[]? preBuffer, byte[]? postBuffer, TransmitFileOptions flags, TransmitFileAsyncResult asyncResult) - { - asyncResult.SetUnmanagedStructures(fileStream, preBuffer, postBuffer, (flags & (TransmitFileOptions.Disconnect | TransmitFileOptions.ReuseSocket)) != 0); - try - { - bool success = TransmitFileHelper( - handle, - fileStream?.SafeFileHandle, - asyncResult.DangerousOverlappedPointer, // SafeHandle was just created in SetUnmanagedStructures - preBuffer is not null ? Marshal.UnsafeAddrOfPinnedArrayElement(preBuffer, 0) : IntPtr.Zero, - preBuffer?.Length ?? 0, - postBuffer is not null ? Marshal.UnsafeAddrOfPinnedArrayElement(postBuffer, 0) : IntPtr.Zero, - postBuffer?.Length ?? 0, - flags); - - return asyncResult.ProcessOverlappedResult(success, 0); - } - catch - { - asyncResult.ReleaseUnmanagedStructures(); - throw; - } - } - public static void CheckDualModeReceiveSupport(Socket socket) { // Dual-mode sockets support received packet info on Windows. diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/TransmitFileAsyncResult.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/TransmitFileAsyncResult.Windows.cs deleted file mode 100644 index b78ad06..0000000 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/TransmitFileAsyncResult.Windows.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; - -namespace System.Net.Sockets -{ - internal sealed class TransmitFileAsyncResult : BaseOverlappedAsyncResult - { - private FileStream? _fileStream; - private bool _doDisconnect; - - internal TransmitFileAsyncResult(Socket socket, object? asyncState, AsyncCallback? asyncCallback) : - base(socket, asyncState, asyncCallback) - { - } - - internal void SetUnmanagedStructures(FileStream? fileStream, byte[]? preBuffer, byte[]? postBuffer, bool doDisconnect) - { - _fileStream = fileStream; - _doDisconnect = doDisconnect; - - int buffsNumber = 0; - - if (preBuffer != null && preBuffer.Length > 0) - ++buffsNumber; - - if (postBuffer != null && postBuffer.Length > 0) - ++buffsNumber; - - object[]? objectsToPin = null; - if (buffsNumber != 0) - { - objectsToPin = new object[buffsNumber]; - - if (preBuffer != null && preBuffer.Length > 0) - { - objectsToPin[--buffsNumber] = preBuffer; - } - - if (postBuffer != null && postBuffer.Length > 0) - { - objectsToPin[--buffsNumber] = postBuffer; - } - } - - base.SetUnmanagedStructures(objectsToPin); - } - - protected override void ForceReleaseUnmanagedStructures() - { - if (_fileStream != null) - { - _fileStream.Dispose(); - _fileStream = null; - } - - base.ForceReleaseUnmanagedStructures(); - } - - internal bool DoDisconnect => _doDisconnect; - } -} diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs index c22b0bc..319403d 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Xunit; @@ -28,12 +27,11 @@ namespace System.Net.Sockets.Tests await Assert.ThrowsAsync(() => SendFileAsync(s, null, null, null, TransmitFileOptions.UseDefaultWorkerThread)); } - [Fact] public async Task NotConnected_ThrowsNotSupportedException() { using Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - + await Assert.ThrowsAsync(() => SendFileAsync(s, null)); await Assert.ThrowsAsync(() => SendFileAsync(s, null, null, null, TransmitFileOptions.UseDefaultWorkerThread)); } @@ -308,7 +306,7 @@ namespace System.Net.Sockets.Tests await disposeTask; SocketError? localSocketError = null; - bool thrownDisposed = false; + bool disposedException = false; try { await socketOperation; @@ -319,18 +317,22 @@ namespace System.Net.Sockets.Tests } catch (ObjectDisposedException) { - thrownDisposed = true; + disposedException = true; } - if (UsesSync) + if (UsesApm) + { + Assert.Null(localSocketError); + Assert.True(disposedException); + } + else if (UsesSync) { Assert.Equal(SocketError.ConnectionAborted, localSocketError); } else { - Assert.True(thrownDisposed); + Assert.Equal(SocketError.OperationAborted, localSocketError); } - // On OSX, we're unable to unblock the on-going socket operations and // perform an abortive close. @@ -415,6 +417,11 @@ namespace System.Net.Sockets.Tests public SendFile_SyncForceNonBlocking(ITestOutputHelper output) : base(output) { } } + public sealed class SendFile_Task : SendFile + { + public SendFile_Task(ITestOutputHelper output) : base(output) { } + } + public sealed class SendFile_Apm : SendFile { public SendFile_Apm(ITestOutputHelper output) : base(output) { } diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketTestHelper.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketTestHelper.cs index 983f7a2..1c27291 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketTestHelper.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketTestHelper.cs @@ -246,8 +246,10 @@ namespace System.Net.Sockets.Tests s.SendAsync(bufferList, SocketFlags.None); public override Task SendToAsync(Socket s, ArraySegment buffer, EndPoint endPoint) => s.SendToAsync(buffer, SocketFlags.None, endPoint); - public override Task SendFileAsync(Socket s, string fileName) => throw new NotSupportedException(); - public override Task SendFileAsync(Socket s, string fileName, ArraySegment preBuffer, ArraySegment postBuffer, TransmitFileOptions flags) => throw new NotSupportedException(); + public override Task SendFileAsync(Socket s, string fileName) => + s.SendFileAsync(fileName).AsTask(); + public override Task SendFileAsync(Socket s, string fileName, ArraySegment preBuffer, ArraySegment postBuffer, TransmitFileOptions flags) => + s.SendFileAsync(fileName, preBuffer, postBuffer, flags).AsTask(); public override Task DisconnectAsync(Socket s, bool reuseSocket) => s.DisconnectAsync(reuseSocket).AsTask(); } @@ -284,8 +286,10 @@ namespace System.Net.Sockets.Tests s.SendAsync(bufferList, SocketFlags.None); public override Task SendToAsync(Socket s, ArraySegment buffer, EndPoint endPoint) => s.SendToAsync(buffer, SocketFlags.None, endPoint, _cts.Token).AsTask() ; - public override Task SendFileAsync(Socket s, string fileName) => throw new NotSupportedException(); - public override Task SendFileAsync(Socket s, string fileName, ArraySegment preBuffer, ArraySegment postBuffer, TransmitFileOptions flags) => throw new NotSupportedException(); + public override Task SendFileAsync(Socket s, string fileName) => + s.SendFileAsync(fileName, _cts.Token).AsTask(); + public override Task SendFileAsync(Socket s, string fileName, ArraySegment preBuffer, ArraySegment postBuffer, TransmitFileOptions flags) => + s.SendFileAsync(fileName, preBuffer, postBuffer, flags, _cts.Token).AsTask(); public override Task DisconnectAsync(Socket s, bool reuseSocket) => s.DisconnectAsync(reuseSocket, _cts.Token).AsTask(); } -- 2.7.4