-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Task- and Span-based Socket.SendFile #45132
Changes from all commits
f0c964a
c501dca
7842909
487fc6c
32e8214
cfebea2
8a8004c
1bf07e1
98e3ace
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,9 @@ public partial class Socket | |
/// <summary>Cached instance for send operations that return <see cref="Task{Int32}"/>.</summary> | ||
private TaskSocketAsyncEventArgs<int>? _multiBufferSendEventArgs; | ||
|
||
/// <summary>Cached instance for file send operations that return <see cref="ValueTask"/>.</summary> | ||
private FileSendSocketAsyncEventargs? _fileSendEventArgs; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we just use one of the existing send instances (above) here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the current state of the PR no, as FileSendSocketAsyncEventargs is special to windows and potential parent classes are sealed. Unsealing them might change perf-characteristics or would this be OK? But I'll try something to unifiy this. Adding the fields to |
||
|
||
internal Task<Socket> AcceptAsync(Socket? acceptSocket) | ||
{ | ||
// Get any cached SocketAsyncEventArg we may have. | ||
|
@@ -356,6 +359,31 @@ internal Task<int> SendToAsync(ArraySegment<byte> buffer, SocketFlags socketFlag | |
return tcs.Task; | ||
} | ||
|
||
public ValueTask SendFileAsync(string? fileName, CancellationToken cancellationToken = default) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doc comments are in gfoidl@feade37 Will merge them once the rest of the PR is OK to avoid unnecessary CI builds. |
||
{ | ||
return SendFileAsync(fileName, default, default, TransmitFileOptions.UseDefaultWorkerThread, cancellationToken); | ||
} | ||
|
||
public ValueTask SendFileAsync(string? fileName, ReadOnlyMemory<byte> preBuffer, ReadOnlyMemory<byte> postBuffer, TransmitFileOptions flags, CancellationToken cancellationToken = default) | ||
{ | ||
ThrowIfDisposed(); | ||
|
||
if (!Connected) | ||
{ | ||
throw new NotSupportedException(SR.net_notconnected); | ||
} | ||
|
||
if (cancellationToken.IsCancellationRequested) | ||
{ | ||
return ValueTask.FromCanceled(cancellationToken); | ||
} | ||
|
||
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"::SendFileAsync() SRC:{LocalEndPoint} DST:{RemoteEndPoint} fileName:{fileName}"); | ||
|
||
FileStream? fileStream = OpenFile(fileName); | ||
return SendFileInternalAsync(fileStream, preBuffer, postBuffer, flags, cancellationToken); | ||
} | ||
|
||
private static void ValidateBufferArguments(byte[] buffer, int offset, int size) | ||
{ | ||
if (buffer == null) | ||
|
@@ -563,6 +591,7 @@ private void DisposeCachedTaskSocketAsyncEventArgs() | |
Interlocked.Exchange(ref _multiBufferSendEventArgs, null)?.Dispose(); | ||
Interlocked.Exchange(ref _singleBufferReceiveEventArgs, null)?.Dispose(); | ||
Interlocked.Exchange(ref _singleBufferSendEventArgs, null)?.Dispose(); | ||
Interlocked.Exchange(ref _fileSendEventArgs, null)?.Dispose(); | ||
} | ||
|
||
/// <summary>A TaskCompletionSource that carries an extra field of strongly-typed state.</summary> | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,6 +5,9 @@ | |||||||||||||||||||||||||||||||
using System.IO; | ||||||||||||||||||||||||||||||||
using System.Threading.Tasks; | ||||||||||||||||||||||||||||||||
using System.Runtime.Versioning; | ||||||||||||||||||||||||||||||||
using System.Threading; | ||||||||||||||||||||||||||||||||
using System.Runtime.CompilerServices; | ||||||||||||||||||||||||||||||||
using System.Threading.Tasks.Sources; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
namespace System.Net.Sockets | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
|
@@ -195,7 +198,7 @@ private static void CheckTransmitFileOptions(TransmitFileOptions flags) | |||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private void SendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postBuffer, TransmitFileOptions flags) | ||||||||||||||||||||||||||||||||
private void SendFileInternal(string? fileName, ReadOnlySpan<byte> preBuffer, ReadOnlySpan<byte> postBuffer, TransmitFileOptions flags) | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
CheckTransmitFileOptions(flags); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
|
@@ -208,7 +211,7 @@ private void SendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postB | |||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
// Send the preBuffer, if any | ||||||||||||||||||||||||||||||||
// This will throw on error | ||||||||||||||||||||||||||||||||
if (preBuffer != null && preBuffer.Length > 0) | ||||||||||||||||||||||||||||||||
if (!preBuffer.IsEmpty) | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
Send(preBuffer); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
@@ -224,55 +227,66 @@ private void SendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postB | |||||||||||||||||||||||||||||||
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) | ||||||||||||||||||||||||||||||||
if (!postBuffer.IsEmpty) | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
Send(postBuffer); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private async Task SendFileInternalAsync(FileStream? fileStream, byte[]? preBuffer, byte[]? postBuffer) | ||||||||||||||||||||||||||||||||
private async ValueTask SendFileInternalAsync(FileStream? fileStream, ReadOnlyMemory<byte> preBuffer, ReadOnlyMemory<byte> postBuffer, TransmitFileOptions flags = TransmitFileOptions.UseDefaultWorkerThread, CancellationToken cancellationToken = default) | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
CheckTransmitFileOptions(flags); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
SocketError errorCode = SocketError.Success; | ||||||||||||||||||||||||||||||||
using (fileStream) | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
// Send the preBuffer, if any | ||||||||||||||||||||||||||||||||
// This will throw on error | ||||||||||||||||||||||||||||||||
if (preBuffer != null && preBuffer.Length > 0) | ||||||||||||||||||||||||||||||||
if (!preBuffer.IsEmpty) | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
// Using "this." makes the extension method kick in | ||||||||||||||||||||||||||||||||
await this.SendAsync(new ArraySegment<byte>(preBuffer), SocketFlags.None).ConfigureAwait(false); | ||||||||||||||||||||||||||||||||
await SendAsync(preBuffer, SocketFlags.None, cancellationToken).ConfigureAwait(false); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Send the file, if any | ||||||||||||||||||||||||||||||||
if (fileStream != null) | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
var tcs = new TaskCompletionSource<SocketError>(); | ||||||||||||||||||||||||||||||||
errorCode = SocketPal.SendFileAsync(_handle, fileStream, (_, socketError) => tcs.SetResult(socketError)); | ||||||||||||||||||||||||||||||||
AsyncValueTaskMethodBuilder<SocketError> taskBuilder = AsyncValueTaskMethodBuilder<SocketError>.Create(); | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of the TaskCompletionSource this one is used and the callback (below) updated to take a state, so no closures need to be allocated. |
||||||||||||||||||||||||||||||||
ValueTask<SocketError> task = taskBuilder.Task; | ||||||||||||||||||||||||||||||||
errorCode = SocketPal.SendFileAsync(_handle, fileStream, taskBuilder, static (state, _, socketError) => state.SetResult(socketError), cancellationToken); | ||||||||||||||||||||||||||||||||
if (errorCode == SocketError.IOPending) | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
errorCode = await tcs.Task.ConfigureAwait(false); | ||||||||||||||||||||||||||||||||
errorCode = await task.ConfigureAwait(false); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
if (errorCode != SocketError.Success) | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
UpdateSendSocketErrorForDisposed(ref errorCode); | ||||||||||||||||||||||||||||||||
UpdateStatusAfterSocketErrorAndThrowException(errorCode); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
if (errorCode == SocketError.OperationAborted) | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of checking for OperationAborted, I think we should just check if the cancellationToken is cancelled and throw if it is (i.e. call cancellationToken.ThrowIfCancellationRequested) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Though we may want to call UpdateStatusAfterSocketError too, not sure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm not sure either, but I think we won't. runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs Lines 4022 to 4036 in 0262b49
|
||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
// Don't wrap into a SocketException | ||||||||||||||||||||||||||||||||
throw new OperationCanceledException(cancellationToken); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
else | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
UpdateStatusAfterSocketErrorAndThrowException(errorCode); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Send the postBuffer, if any | ||||||||||||||||||||||||||||||||
// This will throw on error | ||||||||||||||||||||||||||||||||
if (postBuffer != null && postBuffer.Length > 0) | ||||||||||||||||||||||||||||||||
if (!postBuffer.IsEmpty) | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
// Using "this." makes the extension method kick in | ||||||||||||||||||||||||||||||||
await this.SendAsync(new ArraySegment<byte>(postBuffer), SocketFlags.None).ConfigureAwait(false); | ||||||||||||||||||||||||||||||||
await SendAsync(postBuffer, SocketFlags.None, cancellationToken).ConfigureAwait(false); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
|
@@ -284,12 +298,15 @@ private IAsyncResult BeginSendFileInternal(string? fileName, byte[]? preBuffer, | |||||||||||||||||||||||||||||||
// 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); | ||||||||||||||||||||||||||||||||
return TaskToApm.Begin(SendFileInternalAsync(fileStream, preBuffer, postBuffer).AsTask(), callback, state); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private void EndSendFileInternal(IAsyncResult asyncResult) | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
TaskToApm.End(asyncResult); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
internal sealed class FileSendSocketAsyncEventargs : SocketAsyncEventArgs | ||||||||||||||||||||||||||||||||
{ } | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed or shall
Send
be used for SendFileAsync?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are not exposing the
SocketAsyncEventArgs
-based overload publicly there is no point extending the public API with this option, so let's remove it I guess. (Adding a comment about the workaround whenSocketAsyncOperation.Send
is being assigned.) @geoffkizer agreed?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SocketAsyncEventArgs already has SendPackets, which is effectively a more general version of SendFile. So we shouldn't be adding SendFile to this enum.
I suppose it does raise the question of whether we should have a Task-based SendPacketsAsync method, though...