-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Conversation
Note regarding the This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change. |
@stephentoub PTAL -- I've never been so far down in the internals of networking IO, so I'm not sure if this approach is OK. This PR is created to get some initial feedback (and hopefully hints for the correct route). |
Tagging subscribers to this area: @dotnet/ncl Issue DetailsFor the windows-implementation added a new internal type
|
@@ -443,6 +446,7 @@ public enum SocketAsyncOperation | |||
Send = 7, | |||
SendPackets = 8, | |||
SendTo = 9, | |||
SendFile = 10, |
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 when SocketAsyncOperation.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...
} | ||
|
||
// 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 comment
The 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.
} | ||
|
||
private void EndSendFileInternal(IAsyncResult asyncResult) | ||
{ | ||
TaskToApm.End(asyncResult); | ||
} | ||
|
||
internal sealed class FileSendSocketAsyncEventargs : SocketAsyncEventArgs, IValueTaskSource |
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.
Unfortunately this one is needed here too, to allow
Interlocked.Exchange(ref _fileSendEventArgs, null)?.Dispose(); |
src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs
Outdated
Show resolved
Hide resolved
Doesn't need to be in this PR, but consider setting the TCP_CORK option before sending, and removing it afterward (assuming it wasn't already set). This will make sending pre-/post- buffers more efficient. |
Windows has the (nice) API TransmitFile, which linux doesn't have. As idea the linux-pal could provide a similar api (that in turn does the three "send-calls"), so the managed side can be unified with the use of FileSendSocketAsyncEventargs.
Good point. I'll have a look into this (later today). |
@@ -2012,16 +2024,17 @@ public SocketError SendFileAsync(SafeFileHandle fileHandle, long offset, long co | |||
return errorCode; | |||
} | |||
|
|||
var operation = new SendFileOperation(this) | |||
var operation = new SendFileOperation<TState>(this) |
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.
On Unix we don't cache this operation.
Other operations are cached, the Windows-counterpart is cached too.
Shall it be cached here too or is (re-) creating just fine?
@scalablecory how should this work if there are multiple overlapping |
@@ -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 comment
The 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.
Are overlapping SendFile valid? Do you mean queued? I think sending pre- and post- buffer as separate I/Os has the same issue, right? You'd just have 5 ops to do as one serialized package rather than 3. |
@@ -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 comment
The 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 comment
The 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 SocketAsyncEventArgs
and use AwaitableSocketAsyncEventArgs
, then one field could be re-used.
[InlineData(false, true)] | ||
[InlineData(true, false)] | ||
[InlineData(true, true)] | ||
public async Task SendFileAsync_NoFile_Succeeds(bool usePreBuffer, bool usePostBuffer) |
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.
A lot of other socket tests (e.g. SendReceive, connect, etc) are generalized for different "modes" (sync, various async, span vs no span, etc) by deriving from SocketTestHelperBase and then instantiating appropriate versions of the class. We don't do this currently with SendFile, but since we are adding all these overloads, it probably makes sense to. That means adding SendFile to SocketHelperBase and providing relevant implementations on the derived classes, e.g. SocketHelperApm etc.
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.
I think rebasing tests on SocketTestHelperBase
is a must-have with this change.
Looks like most of the tests can be refactored to the base class (SendFile<T> : SocketTestHelperBase<T> where T : SocketHelperBase
), the ones that are specific to an (APM/Sync/Task) implementation can be moved to the subclass.
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.
Makes sense, but can we do this in a different PR? There is quite some rebasing needed as
public class SendFileTest : FileCleanupTestBase |
FileCleanupTestBase
(I guess this may be a reason why it wasn't done so far.)
} | ||
} | ||
} | ||
|
||
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 comment
The 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
we may want to call UpdateStatusAfterSocketError too, not sure.
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
internal void UpdateStatusAfterSocketError(SocketError errorCode) | |
{ | |
// If we already know the socket is disconnected | |
// we don't need to do anything else. | |
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"errorCode:{errorCode}"); | |
if (_isConnected && (_handle.IsInvalid || (errorCode != SocketError.WouldBlock && | |
errorCode != SocketError.IOPending && errorCode != SocketError.NoBufferSpaceAvailable && | |
errorCode != SocketError.TimedOut))) | |
{ | |
// The socket is no longer a valid socket. | |
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "Invalidating socket."); | |
SetToDisconnected(); | |
} | |
} |
If I'm understanding this correctly, you've taken two different approaches here for Windows and Unix. For Unix, you updated the existing async SendFile path to support Tasks directly. For Windows, you added a new SendFile operation to SocketAsyncEventArgs. Both approaches have their merits, but we should use the same approach for all platforms. This is all complicated by the fact that SocketAsyncEventArgs doesn't actually support a SendFile operation today. It does. however, support SendPackets, which can do everything SendFile does. On Windows this ends up calling TransmitPackets instead of TransmitFile, but I don't think we care about that distinction. So I would suggestion taking one of the following approaches on both Windows and Linux: I think (2) is better long-term, as it would leverage SAEA caching and remove some existing code; but (1) is simpler and addresses the immediate issue. |
BTW, it might make sense to separate out the new sync overloads into a separate PR, just to keep each change more manageable; up to you. |
This is a good point -- I think we have an issue with the Unix impl of SendFile today, since there's no serialization here. If you do a concurrent Send, then that send could slip in between one of the parts of the SendFile and you won't get the expected results. Does that sound right? That said, we don't need to fix this as part of this PR. |
Yep, that's right. The current implementation doesn't have any serialization, nor does this PR.
With SendPackets I don't get the point on how to handle preBuffer and postBuffer. If the unix-pal would provide a similar API than TransmitFile on Windows, we could unifiy the implementation quite easily.
For Unix there was already SendFileInternalAsync, which got updated for the new APIs.
This is basicallay just adding span and forwarding the array-based ones to them. That's why it is in the same PR (it's the easy part). So I'd like to keep them here. |
Possible is using multiple BTW: on Unix there's the same serialization issue. |
I don't think sync needs to be serialized, but async at least should be. |
Why not sync? |
Yeah, that would be great, thanks. |
Yes, that's what I was suggesting. I'm not sure the allocations matter much, but if we had concerns about this, we could cache them. I'd rather see us using the existing SendPackets code than creating a new SendFile operation. |
We need to add Memory support on SendPackets as well: #45267 |
Raised issue for the serialization problem --> #45274 (will comment on other points later...) |
Re: SendPackets If we decide to go this route, #45267 should be done first in order to use the memory-support. Just had a quick look into the possibility on how to implement SendFileAsync via SendPackets. It's relatively straightforward for Windows and *nix. I don't have any feeling how much these APIs will be used and how much concern we should put into avoiding allocations. So maybe it's best to just go with the simplest approach, and improve if there's (real) demand for it? |
update: based on recent discussion this PR should be finished on top of #46975 |
@geoffkizer I think we should be careful here. Many sources and discussions refer to The question is if we want to be fast by default, or deliver a functionally complete solution first, and investigate/improve performance later. Is there any value in a Task-based |
I think this is quite unlikely -- unless we have some additional info, I think we should assume TransmitPackets is as efficient as TransmitFile. The extra allocations for using the SendPackets operation could be a concern; I don't have a strong opinion here. If it's easy to avoid this extra allocation, that would be good. If it's a real pain, I think we could probably live with it as is. |
To move forward my plan looks like:
Does this seem reasonable? |
@gfoidl this plan sounds perfect to me, it seems to deal with all the concerns raised here, please go ahead! |
|
Just started working on the I need a bit more thought on this, but ATM it seems that TransmitFile looks better for windows. |
As I said above,
I'm fine to do (1) for now. Longer-term, we want to achieve the following goals: If we want to punt on (D) for now because it's easier to just implement SendFileAsync directly, that's fine; but eventually we will want to meet all the goals above for SendFileAsync, so let's ensure whatever we do is working towards them, or at least not working away from them. |
Fixes #42591
Fixes #43846
For the windows-implementation added a new internal type
FileSendSocketAsyncEventargs
.As on unix
SendFileAsync
consists of three "send-calls" (one for prebuffer, one for the file, one for the postbuffer) at the moment I see no way to unify the use of FileSendSocketAsyncEventargs for both flavors.