-
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
Implement Socket.DuplicateAndClose() for Windows #1858
Conversation
_rightEndPoint = ep.Create(socketAddress); | ||
} | ||
catch | ||
{ |
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.
It looks suspicious that we silently suppress all exceptions here. What is the motivation?
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 seems to be a bad pattern inherited from the old framework code. Would make sense to replace IPEndPointExtensions.Create()
with a non-throwing IPEndPointExtensions.TryCreate()
, but I did not want to apply changes of that extent in this PR.
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.
If this fails, the socket will be in a bad state. I'd rather we just let it throw.
That said, I don't know why this would fail as I'd expect socket duplication to not work if the address is wrong.
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.
Doesn't that apply for all call sites? If we change the pattern, it's better to alter usages everywhere.
I would prefer to change it in a separate clean-up PR everywhere rather than breaking the (bad) consistency on a single call site.
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 you point to what other code you're comparing to? UDP would function mostly fine like this but to my knowledge this would be the first API where we can have a connected socket without an endpoint set, and the other common connection-oriented APIs (send/recv) don't set it. LocalEndPoint
and RemoteEndPoint
would both just be null forever.
Failing fast here would be better than failing down the line with a null endpoint, especially if it's "free" because the exception is already getting thrown.
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.
The same exception swallowing pattern is present in Send/Receive implementations. I don't get the purpose of those calls though, since we are not doing anything with the remoteEp
instances being returned.
Failing fast would be indeed better here, however the handling of (undocumented & impossiblish) exceptions coming from internals looks quite ad-hoc in existing code. Some call sites just let them leak out, others prefer to swallow them. Shouldn't be there a guideline point about 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.
Removed the try-catch, but I'd really like to have better understanding. The code was taken from Framework.
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs
Show resolved
Hide resolved
Assert.Equal(blocking, clone.Blocking); | ||
} | ||
|
||
public class NotSupportedOnUnix |
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.
It's better not to nest one test class into another. It impairs readability.
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 hierarchical pattern seems actually more readable for me in many cases: helps avoiding long test names, organizing stuff into groups instead. However in this particular case it would be definitely better to ungroup them.
I wonder if we have a written guideline for test code?
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 don't think I've seen this pattern elsewhere in our tests.
Our unit testing is modeled after a book... I believe The Art of Unit Testing. @davidsh can confirm.
src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSADuplicateSocket.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSADuplicateSocket.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSADuplicateSocket.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketInformation.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSADuplicateSocket.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketDuplicationTests.cs
Show resolved
Hide resolved
I think I addressed most review findings, except the ones dealing with existing patterns ( Should I sneak fixes for those into this PR, or shall we do it separately instead? |
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 couple things, otherwise looks good.
src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketInformation.cs
Outdated
Show resolved
Hide resolved
/azp run runtime-libraries outerloop |
Azure Pipelines successfully started running 1 pipeline(s). |
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.
Have we been able to pass these bits off to internal teams to verify?
@@ -0,0 +1,52 @@ | |||
using System; |
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.
nit: copyright
{ | ||
socketInformation = new SocketInformation | ||
{ | ||
ProtocolInformation = new byte[Interop.Winsock.WSAProtocolInfo.Size] |
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.
nit: stackalloc instead of newing up this array
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.
This is an existing public API, that uses byte[]
.
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs
Outdated
Show resolved
Hide resolved
Pushed a change to deal with handle inheritance. The solution is not trivial because of the esoteric behavior of @jkotalik thanks for your engagement here, it prevented a bug! |
@@ -59,6 +59,7 @@ internal ThreadPoolBoundHandle GetOrAllocateThreadPoolBoundHandle(bool trySkipCo | |||
catch (Exception exception) when (!ExceptionCheck.IsFatal(exception)) | |||
{ | |||
bool closed = IsClosed; | |||
bool alreadyBound = !IsInvalid && !IsClosed && (exception is ArgumentException); |
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.
Won't we get an ArgumentException as well if the handle wasn't opened for overlapped I/O? We're assuming that's the case then?
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 made this based this on my understanding of the code in ClrThreadPoolBoundHandle.Windows.cs
:
Lines 30 to 34 in 6ed4a5e
if (ex.HResult == HResults.E_HANDLE) // Bad handle | |
throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle)); | |
if (ex.HResult == HResults.E_INVALIDARG) // Handle already bound or sync handle | |
throw new ArgumentException(SR.Argument_AlreadyBoundOrSyncHandle, nameof(handle)); |
I assume that the handle can not be invalid since we checked it before. Based on the comment, and the exception message, the only other possible reason is: "Handle already bound or sync handle". Let me know, if I'm missing something!
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs
Outdated
Show resolved
Hide resolved
// | ||
// This constructor works in conjunction with DuplicateAndClose, which is not supported on Unix. | ||
// See comments in DuplicateAndClose. | ||
// |
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.
Do we use this style of commenting elsewhere in System.Net.Sockets, with a blank comment line above and below? We've generally moved away from that elsewhere.
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.
Occasionally yes, it was was used in the methods I changed. Getting rid of it.
} | ||
|
||
if (_handle.IsInvalid) | ||
{ |
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 should dispose of state here that may have been created, namely the _handle. Even if it's invalid, it'll likely still trigger finalization. Probably want to null it out as well.
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.
This looks like an unnecessary check, since SocketPal.CreateSocket
already validates handle. Added the disposal there, getting rid of this branch here.
src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs
Outdated
Show resolved
Hide resolved
default System.Net.Sockets.sln configurations to Windows_NT again
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.HandleInformation.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSADuplicateSocket.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSADuplicateSocket.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSADuplicateSocket.cs
Outdated
Show resolved
Hide resolved
|
||
public static (Socket, Socket) CreateConnectedSocketPair() | ||
{ | ||
using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); |
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 there any potential difference in the handling of different address families? Wondering if we need tests for IPv6 sockets as well, or if Windows doesn't discriminate and a test for IPv4 sockets will be sufficient.
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.
To me it looks quite arbitrary whether a functionality is also tested against IPV6 in socket test code. I added a few IPV6 test cases for the duplication to make sure it is covered. See the parameters of DuplicateAndClose_TcpServerHandler
which is emulating most common user scenario.
src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketDuplicationTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketDuplicationTests.cs
Outdated
Show resolved
Hide resolved
Assert.Equal(TestMessage, receivedMessage); | ||
} | ||
|
||
[OuterLoop] // long-running |
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.
What makes it long-running?
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.
For the same reason CreateSocket.CtorAndAccept_SocketNotKeptAliveViaInheritance
is running for about 2 sec in debug on my PC. There are nested child processes + pipes. I'm also wondering why is it that much.
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 should figure out why it's taking 2 seconds. It shouldn't be that long.
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 this also concerns other tests, I'd rather take care of it later separately. I'm preparing a test fix PR anyways.
@@ -30,8 +30,8 @@ Global | |||
{8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Debug|Any CPU.Build.0 = netcoreapp5.0-Windows_NT-Debug|Any CPU | |||
{8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Release|Any CPU.ActiveCfg = netcoreapp5.0-Windows_NT-Release|Any CPU | |||
{8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Release|Any CPU.Build.0 = netcoreapp5.0-Windows_NT-Release|Any CPU | |||
{43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Debug|Any CPU.ActiveCfg = netcoreapp5.0-Unix-Debug|Any CPU | |||
{43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Debug|Any CPU.Build.0 = netcoreapp5.0-Unix-Debug|Any CPU | |||
{43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Debug|Any CPU.ActiveCfg = netcoreapp5.0-Windows_NT-Debug|Any CPU |
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.
@stephentoub all other solutions default to Windows_NT. I suppose you altered this by accident in #2063.
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.
If I modified it, it was VS modifying it automatically. I'd need to look at the various guids, but there is a unix build in this project, so it would make sense for there to be unix entries here.
/azp run runtime-libraries outerloop |
Azure Pipelines successfully started running 1 pipeline(s). |
Fixes #1760.
The implementation is based on Framework code, with the following differences:
InvalidOperationException
is thrown, instead of an undocumentedArgumentException
.SocketInformation
is not[Serializable]
and does not contain a snapshot of RemoteEndpoint. Instead, the endpoint is being restored usinggetsockname
in all cases.