Add Span-based overloads to System.Net.Sockets#22988
Add Span-based overloads to System.Net.Sockets#22988stephentoub merged 2 commits intodotnet:masterfrom
Conversation
|
|
||
| public override int Read(Span<byte> destination) | ||
| { | ||
| if (_cleanedUp) throw new ObjectDisposedException(this.GetType().FullName); |
There was a problem hiding this comment.
Nit: Some of the new methods use this. while others do not.
91a38e1 to
011b735
Compare
|
@mellinoe, another seg fault in the drawing tests, this time on centos: @dotnet-bot test Linux x64 Release Build please |
davidsh
left a comment
There was a problem hiding this comment.
LGTM.
I just want to add that despite writing tests specifically for 'netcoreapp', these new APIs are part of .NET Core 2.1. And that means that they will also be exposed and work on UWP as well (post UWP6.0).
The current architecture of our ref folders means that any API surface that is part of CoreFx repo will be exposed both for 'netcoreapp' and 'uap'. Not a bad thing, per se. But that means we should be testing it. I would prefer in general that we include the extra tests (that have the .netcoreapp.cs suffix) in UAP tests as well. Thus, we will need a UAP test configuration in addition to the 'netstandard' and 'netcoreapp' configs.
cc: @weshaggard @danmosemsft
|
@stephentoub I can't find the segfault in the logs. Do the logs get wiped if you force a re-run? |
|
The jenkins logs don't (though you have to go back and find the previous run of that job), not sure about Helix. |
|
@mellinoe We don't expose it but logs are never actually overwritten. The log you're likely looking for is here. It looks like there's a dumpling bug at play here, as a core dump was created but failed to upload too: |
|
@MattGal Thanks for that link. The crash dump was uploaded, actually. I will see if it gives me any useful information. |
| { | ||
| private get { return (Action<int, byte[], int, SocketFlags, SocketError>)CallbackOrEvent; } | ||
| set { CallbackOrEvent = value; } | ||
| set => CallbackOrEvent = value; |
There was a problem hiding this comment.
This seems like a good change, but we should be consistent about doing it this way across all operations. I'm hacking on this code and can do this in a subsequent change.
| ((Action<int, byte[], int, SocketFlags, SocketError>)CallbackOrEvent)(BytesTransferred, SocketAddress, SocketAddressLen, SocketFlags.None, ErrorCode); | ||
| } | ||
|
|
||
| private sealed class BufferArraySendOperation : SendOperation |
There was a problem hiding this comment.
Do we really need this? Can't we just use BufferPtrSendOperation below?
There was a problem hiding this comment.
We could, but then we'd need to pin the buffer for potentially much longer than we otherwise need to. The byte[] only needs to be pinned during the synchronous call to send, but to use the BufferPtr version, we need to pin it the whole time in order to get the pointer and keep it valid.
| protected override bool DoTryComplete(SocketAsyncContext context) | ||
| { | ||
| int bufferIndex = 0; | ||
| return SocketPal.TryCompleteSendTo(context._socket, new ReadOnlySpan<byte>(BufferPtr, Offset + Count), null, ref bufferIndex, ref Offset, ref Count, Flags, SocketAddress, SocketAddressLen, ref BytesTransferred, out ErrorCode); |
There was a problem hiding this comment.
Why Offset + Count? I would expect this to be new ReadOnlySpan(BufferPtr, Count) -- am I missing something?
There was a problem hiding this comment.
Why Offset + Count?
The TryCompleteSendTo implementation uses the ref Offset, ref Count arguments to determine from where and how much to send, and then updates those values accordingly for a subsequent operation. This means the input span needs to always be one that points to the full original position and size. BufferPtr is the original starting position, and the original length is Offset + Count, even as Offset might be increased and Count might be decreased for partial sends.
There was a problem hiding this comment.
Yeah, I recall now. The usage always confuses me, but it's a separate issue from this PR.
| } | ||
| } | ||
|
|
||
| public unsafe SocketError ReceiveFrom(Span<byte> buffer, ref SocketFlags flags, byte[] socketAddress, ref int socketAddressLen, int timeout, out int bytesReceived) |
There was a problem hiding this comment.
Do we need both this and the existing overload with (byte[], int offset, int count)? We can always go from byte[] -> Span, so why not just have this version and update the callers?
There was a problem hiding this comment.
We can always go from byte[] to span, but we can't go back, and we can't store the span on the heap. Per your question earlier, if we're ok using the BufferPtr variant and pinning the buffer longer than is potentially otherwise needed, then we could consolidate these paths, otherwise I don't see how we can without incurring additional cost.
|
My general concern here is that we're adding Span paths through the code while leaving existing paths with (byte[], int offset, int count) in place. I assume the long-term plan is to use Span (or Buffer) everywhere internally and convert to Span/Buffer at the top edge, right? I realize that cleaning all this up is probably not worthwhile in this PR, but in some cases (e.g. the additions to SocketAsyncContext) it seems worthwhile to do, rather than duplicating existing code. |
I've consolidated where I saw we could do so without incurring potentially non-trivial expense. |
|
Yeah, I understand now that there's a tradeoff re pinning that makes further consolidation questionable. It seems like where we've landed here, the guidance we'd give to customers would be to avoid using Span when they can use (buffer, offset, count) instead. Basically, only use it for native or stackalloc memory. That seems unfortunate to me. Am I missing something? |
You mean specifically with the synchronous socket APIs (rather than span in general)? In reality I don't know if/how much the pinning behavior would actually hurt things; consider, for example, that if the synchronous native call would instead block for the duration of the operation (e.g. in blocking rather than non-blocking mode), the buffer is going to be pinned for that whole P/Invoke any way... so this is really no different than that. I'm just being conservative in how these are implemented in this first go around to avoid negatively impacting existing overloads. |
|
Okay, seems reasonable to me. |
- Socket.Receive/Send - NetworkStream.Read.Write
011b735 to
5e6fa6f
Compare
* Add Span-based overloads to System.Net.Sockets - Socket.Receive/Send - NetworkStream.Read.Write * Address PR feedback Commit migrated from dotnet/corefx@4a97f92
Span-based synchronous overloads of:
Contributes to https://github.com/dotnet/corefx/issues/22608
Contributes to https://github.com/dotnet/corefx/issues/22387
cc: @geoffkizer, @CIPop, @davidsh, @KrzysztofCwalina
(Buffer-based async overloads will be done later once
Buffer<T>is available.)