This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Disable Unix workarounds for Connect'ing to multiple endpoints
On unix, once connect fails on a socket, that socket becomes unusable for further operations, including additional connect attempts. This is at direct odds with Socket's instance Connect methods, some of which allow for multiple connect attempts, either due to multiple IPAddresses being provided or due to a string hostname / DnsEndPoint being provided that could then result in multiple IPAddresses to be tried. We've explored multiple workarounds for this, all of which have problems. The workaround still in the code involves creating a temporary socket for each address, connecting to it, and if that's successful then immediately disconnecting and connecting with the actual socket. But that causes mayhem for a server not expecting that pattern, e.g. that fails if the client disconnects and attempts a reconnect. This leaves us with a few choices, none of which are great: 1. Remove the offending Connect instance methods. Ideally they'd be replaced with static methods, which can be implemented with POSIX-compliant behavior. But these methods are heavily used and work on Windows. 2. Always throw from the instance Connect methods when there's a possibility that multiple addresses will be tried, e.g. from Connect(IPAddress[], ...) but also from Connect(EndPoint) if a DnsEndPoint is specified. This will break a lot of existing code, but it's also predictable, and developers will know quickly when using a problematic API and move away from it to supported patterns. 3. Throw from the Connect methods if multiple addresses are actually supplied, e.g. calling Connect(IPAddress[]) with an array of length 1 would work but an array of length 2 will fail. This will allow a lot more existing code to work, but it's also very unpredictable, e.g. if a string host is provided and gets mapped by DNS to a single IPAddress in the test environment but then multiple IPAddresses in the production environment, everything will work fine locally but then fail in production. 4. When presented with multiple addresses, try the first one, and if it fails, then throw a PlatformNotSupportedException. This may be slightly more predictable than (3), but is still unpredictable, e.g. if a DNS server returns addresses in a different quantity or order from another server. I'm torn between (2) and (3) (and maybe (4)). This commit implements the more conservative (2), as I expect (3) would actually cause serious problems for deployments, but we can loosen from (2) to (3) if that turns out to be better. In the meantime, we should also seriously explore adding static methods for v1 for the same functionality (developers can also write such code themselves), and write guidance and helpers folks can use in the meantime, e.g. pseudo-code: ```C# public static async Task<Socket> Connect( AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, IPAddress[] addresses, int port) { Exception lastExc = null; foreach (IPAddress address in addresses) { Socket s = new Socket(addressFamily, socketType, protocolType); try { await s.ConnectAsync(address, port).ConfigureAwait(false); return s; } catch (Exception exc) { lastExc = exc; } } if (lastExc != null) throw lastExc; throw new ArgumentException("No addresses provided", "addresses"); } ```
- Loading branch information