diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj
index 8db079595b1217..bbaaf1b890477c 100644
--- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj
+++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj
@@ -45,9 +45,7 @@
-
-
@@ -100,7 +98,6 @@
-
@@ -198,7 +195,6 @@
-
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Unix.cs
deleted file mode 100644
index 13b8061a397b0a..00000000000000
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Unix.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Net;
-using System.Runtime.InteropServices;
-using System.Threading;
-using Microsoft.Win32;
-
-namespace System.Net.Sockets
-{
- // ConnectOverlappedAsyncResult - used to take care of storage for async Socket BeginConnect call.
- internal sealed partial class ConnectOverlappedAsyncResult : BaseOverlappedAsyncResult
- {
- public void CompletionCallback(SocketError errorCode)
- {
- CompletionCallback(0, errorCode);
- }
-
- // This method is called by base.CompletionPortCallback base.OverlappedCallback as part of IO completion
- internal override object? PostCompletion(int numBytes)
- {
- var errorCode = (SocketError)ErrorCode;
- if (errorCode == SocketError.Success)
- {
- var socket = (Socket)AsyncObject!;
- socket.SetToConnected();
- return socket;
- }
-
- return null;
- }
- }
-}
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Windows.cs
deleted file mode 100644
index 0288bd648047ad..00000000000000
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.Windows.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace System.Net.Sockets
-{
- // ConnectOverlappedAsyncResult - used to take care of storage for async Socket BeginConnect call.
- internal sealed partial class ConnectOverlappedAsyncResult : BaseOverlappedAsyncResult
- {
- // This method is called by base.CompletionPortCallback base.OverlappedCallback as part of IO completion
- internal override unsafe object? PostCompletion(int numBytes)
- {
- SocketError errorCode = (SocketError)ErrorCode;
- Socket socket = (Socket)AsyncObject!;
-
- if (errorCode == SocketError.Success)
- {
- // Set the socket context.
- try
- {
- errorCode = Interop.Winsock.setsockopt(
- socket.SafeHandle,
- SocketOptionLevel.Socket,
- SocketOptionName.UpdateConnectContext,
- null,
- 0);
- if (errorCode == SocketError.SocketError)
- {
- errorCode = SocketPal.GetLastSocketError();
- }
- }
- catch (ObjectDisposedException)
- {
- errorCode = SocketError.OperationAborted;
- }
-
- ErrorCode = (int)errorCode;
- }
-
- if (errorCode == SocketError.Success)
- {
- socket.SetToConnected();
- return socket;
- }
-
- return null;
- }
- }
-}
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.cs
deleted file mode 100644
index c283707194913d..00000000000000
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/ConnectOverlappedAsyncResult.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace System.Net.Sockets
-{
- // ConnectOverlappedAsyncResult - used to take care of storage for async Socket BeginConnect call.
- internal sealed partial class ConnectOverlappedAsyncResult : BaseOverlappedAsyncResult
- {
- private readonly EndPoint _endPoint;
-
- internal ConnectOverlappedAsyncResult(Socket socket, EndPoint endPoint, object? asyncState, AsyncCallback? asyncCallback) :
- base(socket, asyncState, asyncCallback)
- {
- _endPoint = endPoint;
- }
-
- internal override EndPoint RemoteEndPoint
- {
- get { return _endPoint; }
- }
- }
-}
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/MultipleConnectAsync.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/MultipleConnectAsync.cs
deleted file mode 100644
index 17ca33376cbffd..00000000000000
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/MultipleConnectAsync.cs
+++ /dev/null
@@ -1,527 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-using System.Runtime.ExceptionServices;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace System.Net.Sockets
-{
- // This object is used to wrap a bunch of ConnectAsync operations
- // on behalf of a single user call to ConnectAsync with a DnsEndPoint
- internal abstract class MultipleConnectAsync
- {
- protected SocketAsyncEventArgs? _userArgs;
- protected SocketAsyncEventArgs? _internalArgs;
-
- protected DnsEndPoint? _endPoint;
- protected IPAddress[]? _addressList;
- protected int _nextAddress;
-
- private enum State
- {
- NotStarted,
- DnsQuery,
- ConnectAttempt,
- Completed,
- Canceled,
- }
-
- private State _state;
-
- private readonly object _lockObject = new object();
-
- // Called by Socket to kick off the ConnectAsync process. We'll complete the user's SAEA when it's done.
- // Returns true if the operation is pending, false if it completed synchronously.
- public bool StartConnectAsync(SocketAsyncEventArgs args, DnsEndPoint endPoint)
- {
- IAsyncResult result;
-
- Debug.Assert(!Monitor.IsEntered(_lockObject));
- lock (_lockObject)
- {
- Debug.Assert(endPoint.AddressFamily == AddressFamily.Unspecified ||
- endPoint.AddressFamily == AddressFamily.InterNetwork ||
- endPoint.AddressFamily == AddressFamily.InterNetworkV6,
- $"Unexpected endpoint address family: {endPoint.AddressFamily}");
-
- _userArgs = args;
- _endPoint = endPoint;
-
- // If Cancel() was called before we got the lock, it only set the state to Canceled: we need to
- // fail synchronously from here. Once State.DnsQuery is set, the Cancel() call will handle calling AsyncFail.
- if (_state == State.Canceled)
- {
- SyncFail(new SocketException((int)SocketError.OperationAborted));
- return false;
- }
-
- Debug.Assert(_state == State.NotStarted, "MultipleConnectAsync.StartConnectAsync(): Unexpected object state");
-
- _state = State.DnsQuery;
-
- result = Dns.BeginGetHostAddresses(endPoint.Host, new AsyncCallback(DnsCallback), null);
- }
-
- if (result.CompletedSynchronously)
- {
- return DoDnsCallback(result, true);
- }
- else
- {
- return true;
- }
- }
-
- // Callback which fires when the Dns Resolve is complete
- private void DnsCallback(IAsyncResult result)
- {
- if (!result.CompletedSynchronously)
- {
- DoDnsCallback(result, false);
- }
- }
-
- // Called when the DNS query completes (either synchronously or asynchronously). Checks for failure and
- // starts the first connection attempt if it succeeded.
- // Returns true if the operation is pending, false if it completed synchronously.
- private bool DoDnsCallback(IAsyncResult result, bool sync)
- {
- Exception? exception = null;
- bool pending = false;
-
- Debug.Assert(!Monitor.IsEntered(_lockObject));
- lock (_lockObject)
- {
- // If the connection attempt was canceled during the dns query, the user's callback has already been
- // called asynchronously and we simply need to return.
- if (_state == State.Canceled)
- {
- return true;
- }
-
- Debug.Assert(_state == State.DnsQuery, "MultipleConnectAsync.DoDnsCallback(): Unexpected object state");
-
- try
- {
- _addressList = Dns.EndGetHostAddresses(result);
- Debug.Assert(_addressList != null, "MultipleConnectAsync.DoDnsCallback(): EndGetHostAddresses returned null!");
- }
- catch (Exception e)
- {
- _state = State.Completed;
- exception = e;
- }
-
- // If the dns query succeeded, try to connect to the first address
- if (exception == null)
- {
- _state = State.ConnectAttempt;
-
- _internalArgs = new SocketAsyncEventArgs();
- _internalArgs.Completed += InternalConnectCallback;
- _internalArgs.CopyBufferFrom(_userArgs!);
-
- (exception, pending) = AttemptConnection();
-
- if (exception != null)
- {
- // There was a synchronous error while connecting
- _state = State.Completed;
- }
- }
- }
-
- // Call this outside of the lock because it might call the user's callback.
- if (exception != null)
- {
- return Fail(sync, exception);
- }
- else if (!pending)
- {
- return DoConnectCallback(_internalArgs!, sync);
- }
- else
- {
- return true;
- }
- }
-
- private void InternalConnectCallback(object? sender, SocketAsyncEventArgs args)
- {
- DoConnectCallback(args, false);
- }
-
- // Callback which fires when an internal connection attempt completes.
- // If it failed and there are more addresses to try, do it.
- // Returns true if the operation is pending, false if it completed synchronously.
- private bool DoConnectCallback(SocketAsyncEventArgs args, bool sync)
- {
- Exception? exception = null;
-
- Debug.Assert(!Monitor.IsEntered(_lockObject));
- lock (_lockObject)
- {
- if (_state == State.Canceled)
- {
- // If Cancel was called before we got the lock, the Socket will be closed soon. We need to report
- // OperationAborted (even though the connection actually completed), or the user will try to use a
- // closed Socket.
- exception = new SocketException((int)SocketError.OperationAborted);
- }
- else
- {
- while (true)
- {
- Debug.Assert(_state == State.ConnectAttempt);
-
- if (args.SocketError == SocketError.Success)
- {
- // The connection attempt succeeded; go to the completed state.
- // The callback will be called outside the lock.
- _state = State.Completed;
- break;
- }
- else if (args.SocketError == SocketError.OperationAborted)
- {
- // The socket was closed while the connect was in progress. This can happen if the user
- // closes the socket, and is equivalent to a call to CancelConnectAsync
- exception = new SocketException((int)SocketError.OperationAborted);
- _state = State.Canceled;
- break;
- }
- else
- {
-
- // Keep track of this because it will be overwritten by AttemptConnection
- SocketError currentFailure = args.SocketError;
-
- (Exception? connectException, bool pending) = AttemptConnection();
-
- if (connectException == null)
- {
- if (pending)
- {
- // don't call the callback, another connection attempt is successfully started
- return true;
- }
-
- // We have a sync completion from AttemptConnection.
- // Loop around and process its results.
- }
- else
- {
- SocketException? socketException = connectException as SocketException;
- if (socketException != null && socketException.SocketErrorCode == SocketError.NoData)
- {
- // If the error is NoData, that means there are no more IPAddresses to attempt
- // a connection to. Return the last error from an actual connection instead.
- exception = new SocketException((int)currentFailure);
- }
- else
- {
- exception = connectException;
- }
-
- _state = State.Completed;
- break;
- }
- }
- }
- }
- }
-
- if (exception != null)
- {
- return Fail(sync, exception);
- }
- else
- {
- return Succeed(sync);
- }
- }
-
- // Called to initiate a connection attempt to the next address in the list.
- // Returns (exception, false) if the attempt failed synchronously.
- // Returns (null, true) if pending, or (null, false) if completed synchronously.
- private (Exception? exception, bool pending) AttemptConnection()
- {
- try
- {
- IPAddress? attemptAddress = GetNextAddress(out Socket? attemptSocket);
- if (attemptAddress == null)
- {
- return (new SocketException((int)SocketError.NoData), false);
- }
- Debug.Assert(attemptSocket != null);
-
- SocketAsyncEventArgs args = _internalArgs!;
- args.RemoteEndPoint = new IPEndPoint(attemptAddress, _endPoint!.Port);
- bool pending = attemptSocket.ConnectAsync(args);
- return (null, pending);
- }
- catch (ObjectDisposedException)
- {
- // This can happen if the user closes the socket and is equivalent to a call to CancelConnectAsync.
- return (new SocketException((int)SocketError.OperationAborted), false);
- }
- catch (Exception e)
- {
- return (e, false);
- }
- }
-
- protected abstract void OnSucceed();
-
- private bool Succeed(bool sync)
- {
- OnSucceed();
-
- if (sync)
- {
- _userArgs!.FinishWrapperConnectSyncSuccess(_internalArgs!.ConnectSocket, _internalArgs.BytesTransferred, _internalArgs.SocketFlags);
- }
- else
- {
- _userArgs!.FinishWrapperConnectAsyncSuccess(_internalArgs!.ConnectSocket, _internalArgs.BytesTransferred, _internalArgs.SocketFlags);
- }
-
- _internalArgs.Dispose();
- return !sync;
- }
-
- protected abstract void OnFail(bool abortive);
-
- private bool Fail(bool sync, Exception e)
- {
- if (sync)
- {
- SyncFail(e);
- return false;
- }
- else
- {
- AsyncFail(e);
- return true;
- }
- }
-
- private void SyncFail(Exception e)
- {
- OnFail(false);
-
- if (_internalArgs != null)
- {
- _internalArgs.Dispose();
- }
-
- SocketException? socketException = e as SocketException;
- if (socketException != null)
- {
- _userArgs!.FinishConnectByNameSyncFailure(socketException, 0, SocketFlags.None);
- }
- else
- {
- ExceptionDispatchInfo.Throw(e);
- }
- }
-
- private void AsyncFail(Exception e)
- {
- OnFail(false);
-
- if (_internalArgs != null)
- {
- _internalArgs.Dispose();
- }
-
- _userArgs!.FinishConnectByNameAsyncFailure(e, 0, SocketFlags.None);
- }
-
- public void Cancel()
- {
- bool callOnFail = false;
-
- Debug.Assert(!Monitor.IsEntered(_lockObject));
- lock (_lockObject)
- {
- switch (_state)
- {
- case State.NotStarted:
- // Cancel was called before the Dns query was started. The dns query won't be started
- // and the connection attempt will fail synchronously after the state change to DnsQuery.
- // All we need to do here is close all the sockets.
- callOnFail = true;
- break;
-
- case State.DnsQuery:
- // Cancel was called after the Dns query was started, but before it finished. We can't
- // actually cancel the Dns query, but we'll fake it by failing the connect attempt asynchronously
- // from here, and silently dropping the connection attempt when the Dns query finishes.
- Task.Factory.StartNew(
- s => CallAsyncFail(s),
- null,
- CancellationToken.None,
- TaskCreationOptions.DenyChildAttach,
- TaskScheduler.Default);
-
- callOnFail = true;
- break;
-
- case State.ConnectAttempt:
- // Cancel was called after the Dns query completed, but before we had a connection result to give
- // to the user. Closing the sockets will cause any in-progress ConnectAsync call to fail immediately
- // with OperationAborted, and will cause ObjectDisposedException from any new calls to ConnectAsync
- // (which will be translated to OperationAborted by AttemptConnection).
- callOnFail = true;
- break;
-
- case State.Completed:
- // Cancel was called after we locked in a result to give to the user. Ignore it and give the user
- // the real completion.
- break;
-
- default:
- Debug.Fail($"Unexpected object state: {_state}");
- break;
- }
-
- _state = State.Canceled;
- }
-
- // Call this outside the lock because Socket.Close may block
- if (callOnFail)
- {
- OnFail(true);
- }
- }
-
- // Call AsyncFail on a threadpool thread so it's asynchronous with respect to Cancel().
- private void CallAsyncFail(object? ignored)
- {
- AsyncFail(new SocketException((int)SocketError.OperationAborted));
- }
-
- protected abstract IPAddress? GetNextAddress(out Socket? attemptSocket);
- }
-
- // Used when the instance ConnectAsync method is called, or when the DnsEndPoint specified
- // an AddressFamily. There's only one Socket, and we only try addresses that match its
- // AddressFamily
- internal sealed class SingleSocketMultipleConnectAsync : MultipleConnectAsync
- {
- private readonly Socket _socket;
- private readonly bool _userSocket;
-
- public SingleSocketMultipleConnectAsync(Socket socket, bool userSocket)
- {
- _socket = socket;
- _userSocket = userSocket;
- }
-
- protected override IPAddress? GetNextAddress(out Socket? attemptSocket)
- {
- _socket.ReplaceHandleIfNecessaryAfterFailedConnect();
-
- IPAddress? rval = null;
- do
- {
- if (_nextAddress >= _addressList!.Length)
- {
- attemptSocket = null;
- return null;
- }
-
- rval = _addressList[_nextAddress];
- ++_nextAddress;
- }
- while (!_socket.CanTryAddressFamily(rval.AddressFamily));
-
- attemptSocket = _socket;
- return rval;
- }
-
- protected override void OnFail(bool abortive)
- {
- // Close the socket if this is an abortive failure (CancelConnectAsync)
- // or if we created it internally
- if (abortive || !_userSocket)
- {
- _socket.Dispose();
- }
- }
-
- // nothing to do on success
- protected override void OnSucceed() { }
- }
-
- // This is used when the static ConnectAsync method is called. We don't know the address family
- // ahead of time, so we create both IPv4 and IPv6 sockets.
- internal sealed class DualSocketMultipleConnectAsync : MultipleConnectAsync
- {
- private readonly Socket? _socket4;
- private readonly Socket? _socket6;
-
- public DualSocketMultipleConnectAsync(SocketType socketType, ProtocolType protocolType)
- {
- if (Socket.OSSupportsIPv4)
- {
- _socket4 = new Socket(AddressFamily.InterNetwork, socketType, protocolType);
- }
- if (Socket.OSSupportsIPv6)
- {
- _socket6 = new Socket(AddressFamily.InterNetworkV6, socketType, protocolType);
- }
- }
-
- protected override IPAddress? GetNextAddress(out Socket? attemptSocket)
- {
- IPAddress? rval = null;
- attemptSocket = null;
-
- while (attemptSocket == null)
- {
- if (_nextAddress >= _addressList!.Length)
- {
- return null;
- }
-
- rval = _addressList[_nextAddress];
- ++_nextAddress;
-
- if (rval.AddressFamily == AddressFamily.InterNetworkV6)
- {
- attemptSocket = _socket6;
- }
- else if (rval.AddressFamily == AddressFamily.InterNetwork)
- {
- attemptSocket = _socket4;
- }
- }
-
- attemptSocket?.ReplaceHandleIfNecessaryAfterFailedConnect();
- return rval;
- }
-
- // on success, close the socket that wasn't used
- protected override void OnSucceed()
- {
- if (_socket4 != null && !_socket4.Connected)
- {
- _socket4.Dispose();
- }
- if (_socket6 != null && !_socket6.Connected)
- {
- _socket6.Dispose();
- }
- }
-
- // close both sockets whether its abortive or not - we always create them internally
- protected override void OnFail(bool abortive)
- {
- _socket4?.Dispose();
- _socket6?.Dispose();
- }
- }
-}
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs
index 2318077011db88..6c6655f0ed94ce 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs
@@ -128,36 +128,67 @@ static async ValueTask WaitForConnectWithCancellation(AwaitableSocketAsyncEventA
internal ValueTask ConnectAsync(IPAddress[] addresses, int port, CancellationToken cancellationToken)
{
+ ThrowIfDisposed();
+
if (addresses == null)
{
throw new ArgumentNullException(nameof(addresses));
}
+
if (addresses.Length == 0)
{
throw new ArgumentException(SR.net_invalidAddressList, nameof(addresses));
}
- return DoConnectAsync(addresses, port, cancellationToken);
- }
+ if (!TcpValidationHelpers.ValidatePortNumber(port))
+ {
+ throw new ArgumentOutOfRangeException(nameof(port));
+ }
- private async ValueTask DoConnectAsync(IPAddress[] addresses, int port, CancellationToken cancellationToken)
- {
- Exception? lastException = null;
- foreach (IPAddress address in addresses)
+ if (_isListening)
{
- try
- {
- await ConnectAsync(address, port, cancellationToken).ConfigureAwait(false);
- return;
- }
- catch (Exception ex) when (ex is not OperationCanceledException)
+ throw new InvalidOperationException(SR.net_sockets_mustnotlisten);
+ }
+
+ if (_isConnected)
+ {
+ throw new SocketException((int)SocketError.IsConnected);
+ }
+
+ ValidateForMultiConnect(isMultiEndpoint: false);
+
+ return Core(addresses, port, cancellationToken);
+
+ async ValueTask Core(IPAddress[] addresses, int port, CancellationToken cancellationToken)
+ {
+ Exception? lastException = null;
+ IPEndPoint? endPoint = null;
+ foreach (IPAddress address in addresses)
{
- lastException = ex;
+ try
+ {
+ if (endPoint is null)
+ {
+ endPoint = new IPEndPoint(address, port);
+ }
+ else
+ {
+ endPoint.Address = address;
+ Debug.Assert(endPoint.Port == port);
+ }
+
+ await ConnectAsync(endPoint, cancellationToken).ConfigureAwait(false);
+ return;
+ }
+ catch (Exception ex) when (ex is not OperationCanceledException)
+ {
+ lastException = ex;
+ }
}
- }
- Debug.Assert(lastException != null);
- ExceptionDispatchInfo.Throw(lastException);
+ Debug.Assert(lastException != null);
+ ExceptionDispatchInfo.Throw(lastException);
+ }
}
internal Task ConnectAsync(string host, int port) => ConnectAsync(host, port, default).AsTask();
@@ -735,7 +766,7 @@ public ValueTask ConnectAsync(Socket socket)
try
{
- if (socket.ConnectAsync(this))
+ if (socket.ConnectAsync(this, userSocket: true, saeaCancelable: false))
{
return new ValueTask(this, _token);
}
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs
index 4432190294bc52..7fa5853e579024 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs
@@ -13,10 +13,25 @@ namespace System.Net.Sockets
{
public partial class Socket
{
+ private static CachedSerializedEndPoint? s_cachedAnyEndPoint;
+ private static CachedSerializedEndPoint? s_cachedAnyV6EndPoint;
+ private static CachedSerializedEndPoint? s_cachedMappedAnyV6EndPoint;
private DynamicWinsockMethods? _dynamicWinsockMethods;
internal void ReplaceHandleIfNecessaryAfterFailedConnect() { /* nop on Windows */ }
+ private sealed class CachedSerializedEndPoint
+ {
+ public readonly IPEndPoint IPEndPoint;
+ public readonly Internals.SocketAddress SocketAddress;
+
+ public CachedSerializedEndPoint(IPAddress address)
+ {
+ IPEndPoint = new IPEndPoint(address, 0);
+ SocketAddress = IPEndPointExtensions.Serialize(IPEndPoint);
+ }
+ }
+
[SupportedOSPlatform("windows")]
public Socket(SocketInformation socketInformation)
{
@@ -223,25 +238,26 @@ partial void WildcardBindForConnectIfNecessary(AddressFamily addressFamily)
// The socket must be bound before using ConnectEx.
- IPAddress address;
+ CachedSerializedEndPoint csep;
switch (addressFamily)
{
case AddressFamily.InterNetwork:
- address = IsDualMode ? s_IPAddressAnyMapToIPv6 : IPAddress.Any;
+ csep = IsDualMode ?
+ s_cachedMappedAnyV6EndPoint ??= new CachedSerializedEndPoint(s_IPAddressAnyMapToIPv6) :
+ s_cachedAnyEndPoint ??= new CachedSerializedEndPoint(IPAddress.Any);
break;
case AddressFamily.InterNetworkV6:
- address = IPAddress.IPv6Any;
+ csep = s_cachedAnyV6EndPoint ??= new CachedSerializedEndPoint(IPAddress.IPv6Any);
break;
default:
return;
}
- if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, address);
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, csep.IPEndPoint);
- var endPoint = new IPEndPoint(address, 0);
- DoBind(endPoint, IPEndPointExtensions.Serialize(endPoint));
+ DoBind(csep.IPEndPoint, csep.SocketAddress);
}
internal unsafe bool ConnectEx(SafeSocketHandle socketHandle,
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs
index dde46961d16e88..1496abb598a8b2 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs
@@ -5,15 +5,13 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using System.Diagnostics.Tracing;
-using System.Globalization;
using System.IO;
using System.Net.Internals;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
-using System.Runtime.InteropServices;
-using System.Threading;
using System.Runtime.Versioning;
+using System.Threading;
+using System.Threading.Tasks;
namespace System.Net.Sockets
{
@@ -64,7 +62,6 @@ public partial class Socket : IDisposable
private class CacheSet
{
- internal CallbackClosure? ConnectClosureCache;
internal CallbackClosure? AcceptClosureCache;
internal CallbackClosure? SendClosureCache;
internal CallbackClosure? ReceiveClosureCache;
@@ -2056,220 +2053,17 @@ public static void Select(IList? checkRead, IList? checkWrite, IList? checkError
}
}
- // Routine Description:
- //
- // BeginConnect - Does an async connect.
- //
- // Arguments:
- //
- // remoteEP - status line that we wish to parse
- // Callback - Async Callback Delegate that is called upon Async Completion
- // State - State used to track callback, set by caller, not required
- //
- // Return Value:
- //
- // IAsyncResult - Async result used to retrieve result
- public IAsyncResult BeginConnect(EndPoint remoteEP, AsyncCallback? callback, object? state)
- {
- // Validate input parameters.
- ThrowIfDisposed();
-
- if (remoteEP == null)
- {
- throw new ArgumentNullException(nameof(remoteEP));
- }
-
- if (_isListening)
- {
- throw new InvalidOperationException(SR.net_sockets_mustnotlisten);
- }
-
- if (_isConnected)
- {
- throw new SocketException((int)SocketError.IsConnected);
- }
-
-
- DnsEndPoint? dnsEP = remoteEP as DnsEndPoint;
- if (dnsEP != null)
- {
- ValidateForMultiConnect(isMultiEndpoint: true); // needs to come before CanTryAddressFamily call
-
- if (dnsEP.AddressFamily != AddressFamily.Unspecified && !CanTryAddressFamily(dnsEP.AddressFamily))
- {
- throw new NotSupportedException(SR.net_invalidversion);
- }
-
- return BeginConnect(dnsEP.Host, dnsEP.Port, callback, state);
- }
-
- ValidateForMultiConnect(isMultiEndpoint: false);
- return UnsafeBeginConnect(remoteEP, callback, state, flowContext: true);
- }
-
- private bool CanUseConnectEx(EndPoint remoteEP)
- {
- Debug.Assert(remoteEP.GetType() != typeof(DnsEndPoint));
-
- // ConnectEx supports connection-oriented sockets.
- // The socket must be bound before calling ConnectEx.
- // In case of IPEndPoint, the Socket will be bound using WildcardBindForConnectIfNecessary.
- // Unix sockets are not supported by ConnectEx.
-
- return (_socketType == SocketType.Stream) &&
- (_rightEndPoint != null || remoteEP.GetType() == typeof(IPEndPoint)) &&
- (remoteEP.AddressFamily != AddressFamily.Unix);
- }
-
- internal IAsyncResult UnsafeBeginConnect(EndPoint remoteEP, AsyncCallback? callback, object? state, bool flowContext = false)
- {
- if (CanUseConnectEx(remoteEP))
- {
- return BeginConnectEx(remoteEP, flowContext, callback, state);
- }
-
- EndPoint endPointSnapshot = remoteEP;
- var asyncResult = new ConnectAsyncResult(this, endPointSnapshot, state, callback);
-
- // For connectionless protocols, Connect is not an I/O call.
- Connect(remoteEP);
- asyncResult.FinishPostingAsyncOp();
-
- // Synchronously complete the I/O and call the user's callback.
- asyncResult.InvokeCallback();
- return asyncResult;
- }
-
- public IAsyncResult BeginConnect(string host, int port, AsyncCallback? requestCallback, object? state)
- {
- ThrowIfDisposed();
-
- if (host == null)
- {
- throw new ArgumentNullException(nameof(host));
- }
- if (!TcpValidationHelpers.ValidatePortNumber(port))
- {
- throw new ArgumentOutOfRangeException(nameof(port));
- }
- if (_addressFamily != AddressFamily.InterNetwork && _addressFamily != AddressFamily.InterNetworkV6)
- {
- throw new NotSupportedException(SR.net_invalidversion);
- }
-
- if (_isListening)
- {
- throw new InvalidOperationException(SR.net_sockets_mustnotlisten);
- }
-
- if (_isConnected)
- {
- throw new SocketException((int)SocketError.IsConnected);
- }
-
- IPAddress? parsedAddress;
- if (IPAddress.TryParse(host, out parsedAddress))
- {
- return BeginConnect(parsedAddress, port, requestCallback, state);
- }
-
- ValidateForMultiConnect(isMultiEndpoint: true);
-
- // Here, want to flow the context. No need to lock.
- MultipleAddressConnectAsyncResult result = new MultipleAddressConnectAsyncResult(null, port, this, state, requestCallback);
- result.StartPostingAsyncOp(false);
-
- IAsyncResult dnsResult = Dns.BeginGetHostAddresses(host, new AsyncCallback(DnsCallback), result);
- if (dnsResult.CompletedSynchronously)
- {
- if (DoDnsCallback(dnsResult, result))
- {
- result.InvokeCallback();
- }
- }
-
- // Done posting.
- result.FinishPostingAsyncOp(ref Caches.ConnectClosureCache);
-
- return result;
- }
-
- public IAsyncResult BeginConnect(IPAddress address, int port, AsyncCallback? requestCallback, object? state)
- {
- ThrowIfDisposed();
-
- if (address == null)
- {
- throw new ArgumentNullException(nameof(address));
- }
- if (!TcpValidationHelpers.ValidatePortNumber(port))
- {
- throw new ArgumentOutOfRangeException(nameof(port));
- }
-
- if (_isConnected)
- {
- throw new SocketException((int)SocketError.IsConnected);
- }
-
- ValidateForMultiConnect(isMultiEndpoint: false); // needs to be called before CanTryAddressFamily
-
- if (!CanTryAddressFamily(address.AddressFamily))
- {
- throw new NotSupportedException(SR.net_invalidversion);
- }
-
- return BeginConnect(new IPEndPoint(address, port), requestCallback, state);
- }
-
- public IAsyncResult BeginConnect(IPAddress[] addresses, int port, AsyncCallback? requestCallback, object? state)
- {
- ThrowIfDisposed();
-
- if (addresses == null)
- {
- throw new ArgumentNullException(nameof(addresses));
- }
- if (addresses.Length == 0)
- {
- throw new ArgumentException(SR.net_invalidAddressList, nameof(addresses));
- }
- if (!TcpValidationHelpers.ValidatePortNumber(port))
- {
- throw new ArgumentOutOfRangeException(nameof(port));
- }
- if (_addressFamily != AddressFamily.InterNetwork && _addressFamily != AddressFamily.InterNetworkV6)
- {
- throw new NotSupportedException(SR.net_invalidversion);
- }
-
- if (_isListening)
- {
- throw new InvalidOperationException(SR.net_sockets_mustnotlisten);
- }
+ public IAsyncResult BeginConnect(EndPoint remoteEP, AsyncCallback? callback, object? state) =>
+ TaskToApm.Begin(ConnectAsync(remoteEP), callback, state);
- if (_isConnected)
- {
- throw new SocketException((int)SocketError.IsConnected);
- }
-
- ValidateForMultiConnect(isMultiEndpoint: true);
+ public IAsyncResult BeginConnect(string host, int port, AsyncCallback? requestCallback, object? state) =>
+ TaskToApm.Begin(ConnectAsync(host, port), requestCallback, state);
- // Set up the result to capture the context. No need for a lock.
- MultipleAddressConnectAsyncResult result = new MultipleAddressConnectAsyncResult(addresses, port, this, state, requestCallback);
- result.StartPostingAsyncOp(false);
-
- if (DoMultipleAddressConnectCallback(PostOneBeginConnect(result), result))
- {
- // If the call completes synchronously, invoke the callback from here.
- result.InvokeCallback();
- }
+ public IAsyncResult BeginConnect(IPAddress address, int port, AsyncCallback? requestCallback, object? state) =>
+ TaskToApm.Begin(ConnectAsync(address, port), requestCallback, state);
- // Finished posting async op. Possibly will call callback.
- result.FinishPostingAsyncOp(ref Caches.ConnectClosureCache);
-
- return result;
- }
+ public IAsyncResult BeginConnect(IPAddress[] addresses, int port, AsyncCallback? requestCallback, object? state) =>
+ TaskToApm.Begin(ConnectAsync(addresses, port), requestCallback, state);
public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback? callback, object? state)
{
@@ -2330,93 +2124,10 @@ public void Disconnect(bool reuseSocket)
_localEndPoint = null;
}
- // Routine Description:
- //
- // EndConnect - Called after receiving callback from BeginConnect,
- // in order to retrieve the result of async call
- //
- // Arguments:
- //
- // AsyncResult - the AsyncResult Returned from BeginConnect call
- //
- // Return Value:
- //
- // int - Return code from async Connect, 0 for success, SocketError.NotConnected otherwise
public void EndConnect(IAsyncResult asyncResult)
{
- // There are three AsyncResult types we support in EndConnect:
- // - ConnectAsyncResult - a fully synchronous operation that already completed, wrapped in an AsyncResult
- // - MultipleAddressConnectAsyncResult - a parent operation for other Connects (connecting to DnsEndPoint)
- // - ConnectOverlappedAsyncResult - a connect to an IPEndPoint
- // For Telemetry, we already logged everything for ConnectAsyncResult in DoConnect,
- // and we want to avoid logging duplicated events for MultipleAddressConnect.
- // Therefore, we always check that asyncResult is ConnectOverlapped before logging.
-
- if (Disposed)
- {
- if (SocketsTelemetry.Log.IsEnabled() && asyncResult is ConnectOverlappedAsyncResult)
- {
- SocketsTelemetry.Log.AfterConnect(SocketError.NotSocket);
- }
-
- ThrowObjectDisposedException();
- }
-
- // Validate input parameters.
- if (asyncResult == null)
- {
- throw new ArgumentNullException(nameof(asyncResult));
- }
-
- ContextAwareResult? castedAsyncResult =
- asyncResult as ConnectOverlappedAsyncResult ??
- asyncResult as MultipleAddressConnectAsyncResult ??
- (ContextAwareResult?)(asyncResult as ConnectAsyncResult);
-
- if (castedAsyncResult == null || castedAsyncResult.AsyncObject != this)
- {
- throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult));
- }
- if (castedAsyncResult.EndCalled)
- {
- throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, "EndConnect"));
- }
-
- castedAsyncResult.InternalWaitForCompletion();
- castedAsyncResult.EndCalled = true;
-
- if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"asyncResult:{asyncResult}");
-
- Exception? ex = castedAsyncResult.Result as Exception;
-
- if (ex != null || (SocketError)castedAsyncResult.ErrorCode != SocketError.Success)
- {
- SocketError errorCode = (SocketError)castedAsyncResult.ErrorCode;
-
- if (ex == null)
- {
- UpdateConnectSocketErrorForDisposed(ref errorCode);
- // Update the internal state of this socket according to the error before throwing.
- SocketException se = SocketExceptionFactory.CreateSocketException((int)errorCode, castedAsyncResult.RemoteEndPoint);
- UpdateStatusAfterSocketError(se);
- ex = se;
- }
-
- if (SocketsTelemetry.Log.IsEnabled() && castedAsyncResult is ConnectOverlappedAsyncResult)
- {
- SocketsTelemetry.Log.AfterConnect(errorCode, ex.Message);
- }
-
- if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, ex);
- ExceptionDispatchInfo.Throw(ex);
- }
-
- if (SocketsTelemetry.Log.IsEnabled() && castedAsyncResult is ConnectOverlappedAsyncResult)
- {
- SocketsTelemetry.Log.AfterConnect(SocketError.Success);
- }
-
- if (NetEventSource.Log.IsEnabled()) NetEventSource.Connected(this, LocalEndPoint, RemoteEndPoint);
+ ThrowIfDisposed();
+ TaskToApm.End(asyncResult);
}
public void EndDisconnect(IAsyncResult asyncResult)
@@ -3712,9 +3423,9 @@ public bool AcceptAsync(SocketAsyncEventArgs e)
}
public bool ConnectAsync(SocketAsyncEventArgs e) =>
- ConnectAsync(e, userSocket: true);
+ ConnectAsync(e, userSocket: true, saeaCancelable: true);
- private bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket)
+ internal bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket, bool saeaCancelable)
{
bool pending;
@@ -3757,14 +3468,11 @@ private bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket)
throw new NotSupportedException(SR.net_invalidversion);
}
- MultipleConnectAsync multipleConnectAsync = new SingleSocketMultipleConnectAsync(this, userSocket: true);
-
e.StartOperationCommon(this, SocketAsyncOperation.Connect);
- e.StartOperationConnect(multipleConnectAsync, userSocket: true);
-
+ e.StartOperationConnect(saeaCancelable, userSocket);
try
{
- pending = multipleConnectAsync.StartConnectAsync(e, dnsEP);
+ pending = e.DnsConnectAsync(dnsEP, default, default);
}
catch
{
@@ -3788,10 +3496,7 @@ private bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket)
// Save the old RightEndPoint and prep new RightEndPoint.
EndPoint? oldEndPoint = _rightEndPoint;
- if (_rightEndPoint == null)
- {
- _rightEndPoint = endPointSnapshot;
- }
+ _rightEndPoint ??= endPointSnapshot;
if (SocketsTelemetry.Log.IsEnabled())
{
@@ -3800,21 +3505,17 @@ private bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket)
// Prepare for the native call.
e.StartOperationCommon(this, SocketAsyncOperation.Connect);
- e.StartOperationConnect(multipleConnect: null, userSocket);
+ e.StartOperationConnect(saeaMultiConnectCancelable: false, userSocket);
// Make the native call.
- SocketError socketError;
try
{
- if (CanUseConnectEx(endPointSnapshot))
- {
- socketError = e.DoOperationConnectEx(this, _handle);
- }
- else
- {
- // For connectionless protocols, Connect is not an I/O call.
- socketError = e.DoOperationConnect(this, _handle);
- }
+ // ConnectEx supports connection-oriented sockets but not UDS. The socket must be bound before calling ConnectEx.
+ bool canUseConnectEx = _socketType == SocketType.Stream && endPointSnapshot.AddressFamily != AddressFamily.Unix;
+ SocketError socketError = canUseConnectEx ?
+ e.DoOperationConnectEx(this, _handle) :
+ e.DoOperationConnect(this, _handle); // For connectionless protocols, Connect is not an I/O call.
+ pending = socketError == SocketError.IOPending;
}
catch (Exception ex)
{
@@ -3830,8 +3531,6 @@ private bool ConnectAsync(SocketAsyncEventArgs e, bool userSocket)
e.Complete();
throw;
}
-
- pending = (socketError == SocketError.IOPending);
}
return pending;
@@ -3859,27 +3558,12 @@ public static bool ConnectAsync(SocketType socketType, ProtocolType protocolType
if (dnsEP != null)
{
- Socket? attemptSocket = null;
- MultipleConnectAsync? multipleConnectAsync = null;
- if (dnsEP.AddressFamily == AddressFamily.Unspecified)
- {
- // This is the only *Connect* API that fully supports multiple endpoint attempts, as it's responsible
- // for creating each Socket instance and can create one per attempt.
- multipleConnectAsync = new DualSocketMultipleConnectAsync(socketType, protocolType);
-#pragma warning restore
- }
- else
- {
- attemptSocket = new Socket(dnsEP.AddressFamily, socketType, protocolType);
- multipleConnectAsync = new SingleSocketMultipleConnectAsync(attemptSocket, userSocket: false);
- }
-
+ Socket? attemptSocket = dnsEP.AddressFamily != AddressFamily.Unspecified ? new Socket(dnsEP.AddressFamily, socketType, protocolType) : null;
e.StartOperationCommon(attemptSocket, SocketAsyncOperation.Connect);
- e.StartOperationConnect(multipleConnectAsync, userSocket: false);
-
+ e.StartOperationConnect(saeaMultiConnectCancelable: true, userSocket: false);
try
{
- pending = multipleConnectAsync.StartConnectAsync(e, dnsEP);
+ pending = e.DnsConnectAsync(dnsEP, socketType, protocolType);
}
catch
{
@@ -3890,7 +3574,7 @@ public static bool ConnectAsync(SocketType socketType, ProtocolType protocolType
else
{
Socket attemptSocket = new Socket(endPointSnapshot.AddressFamily, socketType, protocolType);
- pending = attemptSocket.ConnectAsync(e, userSocket: false);
+ pending = attemptSocket.ConnectAsync(e, userSocket: false, saeaCancelable: true);
}
return pending;
@@ -4632,277 +4316,6 @@ internal void InternalSetBlocking(bool desired)
InternalSetBlocking(desired, out current);
}
- // Implements ConnectEx - this provides completion port IO and support for disconnect and reconnects.
- // Since this is private, the unsafe mode is specified with a flag instead of an overload.
- private IAsyncResult BeginConnectEx(EndPoint remoteEP, bool flowContext, AsyncCallback? callback, object? state)
- {
- EndPoint endPointSnapshot = remoteEP;
- Internals.SocketAddress socketAddress = Serialize(ref endPointSnapshot);
-
- if (SocketsTelemetry.Log.IsEnabled())
- {
- SocketsTelemetry.Log.ConnectStart(socketAddress);
-
- // Ignore flowContext when using Telemetry to avoid losing Activity tracking
- flowContext = true;
- }
-
- WildcardBindForConnectIfNecessary(endPointSnapshot.AddressFamily);
-
- // Allocate the async result and the event we'll pass to the thread pool.
- ConnectOverlappedAsyncResult asyncResult = new ConnectOverlappedAsyncResult(this, endPointSnapshot, state, callback);
-
- // If context flowing is enabled, set it up here. No need to lock since the context isn't used until the callback.
- if (flowContext)
- {
- asyncResult.StartPostingAsyncOp(false);
- }
-
- EndPoint? oldEndPoint = _rightEndPoint;
- if (_rightEndPoint == null)
- {
- _rightEndPoint = endPointSnapshot;
- }
-
- SocketError errorCode;
- try
- {
- errorCode = SocketPal.ConnectAsync(this, _handle, socketAddress.Buffer, socketAddress.Size, asyncResult);
- }
- catch (Exception ex)
- {
- if (SocketsTelemetry.Log.IsEnabled())
- {
- SocketsTelemetry.Log.AfterConnect(SocketError.NotSocket, ex.Message);
- }
-
- // _rightEndPoint will always equal oldEndPoint.
- _rightEndPoint = oldEndPoint;
- _localEndPoint = null;
- throw;
- }
-
- if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"ConnectAsync returns:{errorCode}");
-
- if (errorCode == SocketError.Success)
- {
- // Synchronous success. Indicate that we're connected.
- SetToConnected();
- }
-
- if (!CheckErrorAndUpdateStatus(errorCode))
- {
- UpdateConnectSocketErrorForDisposed(ref errorCode);
- // Update the internal state of this socket according to the error before throwing.
- _rightEndPoint = oldEndPoint;
- _localEndPoint = null;
-
- if (SocketsTelemetry.Log.IsEnabled()) SocketsTelemetry.Log.AfterConnect(errorCode);
-
- throw new SocketException((int)errorCode);
- }
-
- // We didn't throw, so indicate that we're returning this result to the user. This may call the callback.
- // This is a nop if the context isn't being flowed.
- asyncResult.FinishPostingAsyncOp(ref Caches.ConnectClosureCache);
-
- if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"{endPointSnapshot} returning AsyncResult:{asyncResult}");
- return asyncResult;
- }
-
- private static void DnsCallback(IAsyncResult result)
- {
- if (result.CompletedSynchronously)
- {
- return;
- }
-
- bool invokeCallback = false;
-
- MultipleAddressConnectAsyncResult context = (MultipleAddressConnectAsyncResult)result.AsyncState!;
- try
- {
- invokeCallback = DoDnsCallback(result, context);
- }
- catch (Exception exception)
- {
- context.InvokeCallback(exception);
- }
-
- // Invoke the callback outside of the try block so we don't catch user exceptions.
- if (invokeCallback)
- {
- context.InvokeCallback();
- }
- }
-
- private static bool DoDnsCallback(IAsyncResult result, MultipleAddressConnectAsyncResult context)
- {
- IPAddress[] addresses = Dns.EndGetHostAddresses(result);
- context._addresses = addresses;
- return DoMultipleAddressConnectCallback(PostOneBeginConnect(context), context);
- }
-
- private sealed class ConnectAsyncResult : ContextAwareResult
- {
- private readonly EndPoint _endPoint;
-
- internal ConnectAsyncResult(object myObject, EndPoint endPoint, object? myState, AsyncCallback? myCallBack) :
- base(myObject, myState, myCallBack)
- {
- _endPoint = endPoint;
- }
-
- internal override EndPoint RemoteEndPoint
- {
- get { return _endPoint; }
- }
- }
-
- private sealed class MultipleAddressConnectAsyncResult : ContextAwareResult
- {
- internal MultipleAddressConnectAsyncResult(IPAddress[]? addresses, int port, Socket socket, object? myState, AsyncCallback? myCallBack) :
- base(socket, myState, myCallBack)
- {
- _addresses = addresses;
- _port = port;
- _socket = socket;
- }
-
- internal Socket _socket; // Keep this member just to avoid all the casting.
- internal IPAddress[]? _addresses;
- internal int _index;
- internal int _port;
- internal Exception? _lastException;
-
- internal override EndPoint? RemoteEndPoint
- {
- get
- {
- if (_addresses != null && _index > 0 && _index < _addresses.Length)
- {
- return new IPEndPoint(_addresses[_index], _port);
- }
- else
- {
- return null;
- }
- }
- }
- }
-
- private static AsyncCallback? s_multipleAddressConnectCallback;
- private static AsyncCallback CachedMultipleAddressConnectCallback
- {
- get
- {
- if (s_multipleAddressConnectCallback == null)
- {
- s_multipleAddressConnectCallback = new AsyncCallback(MultipleAddressConnectCallback);
- }
- return s_multipleAddressConnectCallback;
- }
- }
-
- private static object? PostOneBeginConnect(MultipleAddressConnectAsyncResult context)
- {
- IPAddress currentAddressSnapshot = context._addresses![context._index];
-
- context._socket.ReplaceHandleIfNecessaryAfterFailedConnect();
-
- if (!context._socket.CanTryAddressFamily(currentAddressSnapshot.AddressFamily))
- {
- return context._lastException != null ? context._lastException : new ArgumentException(SR.net_invalidAddressList, nameof(context));
- }
-
- try
- {
- EndPoint endPoint = new IPEndPoint(currentAddressSnapshot, context._port);
-
- context._socket.Serialize(ref endPoint);
-
- IAsyncResult connectResult = context._socket.UnsafeBeginConnect(endPoint, CachedMultipleAddressConnectCallback, context);
- if (connectResult.CompletedSynchronously)
- {
- return connectResult;
- }
- }
- catch (Exception exception) when (!(exception is OutOfMemoryException))
- {
- return exception;
- }
-
- return null;
- }
-
- private static void MultipleAddressConnectCallback(IAsyncResult result)
- {
- if (result.CompletedSynchronously)
- {
- return;
- }
-
- bool invokeCallback = false;
-
- MultipleAddressConnectAsyncResult context = (MultipleAddressConnectAsyncResult)result.AsyncState!;
- try
- {
- invokeCallback = DoMultipleAddressConnectCallback(result, context);
- }
- catch (Exception exception)
- {
- context.InvokeCallback(exception);
- }
-
- // Invoke the callback outside of the try block so we don't catch user Exceptions.
- if (invokeCallback)
- {
- context.InvokeCallback();
- }
- }
-
- // This is like a regular async callback worker, except the result can be an exception. This is a useful pattern when
- // processing should continue whether or not an async step failed.
- private static bool DoMultipleAddressConnectCallback(object? result, MultipleAddressConnectAsyncResult context)
- {
- while (result != null)
- {
- Exception? ex = result as Exception;
- if (ex == null)
- {
- try
- {
- context._socket.EndConnect((IAsyncResult)result);
- }
- catch (Exception exception)
- {
- ex = exception;
- }
- }
-
- if (ex == null)
- {
- // Don't invoke the callback from here, because we're probably inside
- // a catch-all block that would eat exceptions from the callback.
- // Instead tell our caller to invoke the callback outside of its catchall.
- return true;
- }
- else
- {
- if (++context._index >= context._addresses!.Length)
- {
- ExceptionDispatchInfo.Throw(ex);
- }
-
- context._lastException = ex;
- result = PostOneBeginConnect(context);
- }
- }
-
- // Don't invoke the callback at all, because we've posted another async connection attempt.
- return false;
- }
-
// CreateAcceptSocket - pulls unmanaged results and assembles them into a new Socket object.
internal Socket CreateAcceptSocket(SafeSocketHandle fd, EndPoint remoteEP)
{
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs
index 7f86d700dc1894..fa5b1b2b597c22 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs
@@ -159,7 +159,7 @@ private unsafe SocketError ProcessIOCPResult(bool success, int bytesTransferred,
FreeNativeOverlapped(overlapped);
FinishOperationSyncSuccess(bytesTransferred, SocketFlags.None);
- if (SocketsTelemetry.Log.IsEnabled()) AfterConnectAcceptTelemetry();
+ if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) AfterConnectAcceptTelemetry();
return SocketError.Success;
}
@@ -177,7 +177,7 @@ private unsafe SocketError ProcessIOCPResult(bool success, int bytesTransferred,
FreeNativeOverlapped(overlapped);
FinishOperationSyncFailure(socketError, bytesTransferred, SocketFlags.None);
- if (SocketsTelemetry.Log.IsEnabled()) AfterConnectAcceptTelemetry();
+ if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) AfterConnectAcceptTelemetry();
return socketError;
}
@@ -212,7 +212,7 @@ private unsafe SocketError ProcessIOCPResultWithSingleBufferHandle(SocketError s
FreeNativeOverlapped(overlapped);
FinishOperationSyncSuccess(bytesTransferred, SocketFlags.None);
- if (SocketsTelemetry.Log.IsEnabled()) AfterConnectAcceptTelemetry();
+ if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) AfterConnectAcceptTelemetry();
return SocketError.Success;
}
@@ -231,7 +231,7 @@ private unsafe SocketError ProcessIOCPResultWithSingleBufferHandle(SocketError s
FreeNativeOverlapped(overlapped);
FinishOperationSyncFailure(socketError, bytesTransferred, SocketFlags.None);
- if (SocketsTelemetry.Log.IsEnabled()) AfterConnectAcceptTelemetry();
+ if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) AfterConnectAcceptTelemetry();
return socketError;
}
diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs
index e85bb70d70148a..89a9bfffb1bc98 100644
--- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs
+++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs
@@ -3,9 +3,10 @@
using System.Collections.Generic;
using System.Diagnostics;
-using System.Diagnostics.Tracing;
using System.Runtime.InteropServices;
using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Sources;
namespace System.Net.Sockets
{
@@ -73,6 +74,7 @@ public partial class SocketAsyncEventArgs : EventArgs, IDisposable
private Socket? _currentSocket;
private bool _userSocket; // if false when performing Connect, _currentSocket should be disposed
private bool _disposeCalled;
+ private protected bool _disableTelemetry;
// Controls thread safety via Interlocked.
private const int Configuring = -1;
@@ -81,7 +83,7 @@ public partial class SocketAsyncEventArgs : EventArgs, IDisposable
private const int Disposed = 2;
private int _operating;
- private MultipleConnectAsync? _multipleConnect;
+ private CancellationTokenSource? _multipleConnectCancellation;
public SocketAsyncEventArgs() : this(unsafeSuppressExecutionContextFlow: false)
{
@@ -200,10 +202,7 @@ public int BytesTransferred
private void OnCompletedInternal()
{
- if (SocketsTelemetry.Log.IsEnabled())
- {
- AfterConnectAcceptTelemetry();
- }
+ if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) AfterConnectAcceptTelemetry();
OnCompleted(this);
}
@@ -222,10 +221,7 @@ private void AfterConnectAcceptTelemetry()
break;
case SocketAsyncOperation.Connect:
- if (_multipleConnect is null)
- {
- SocketsTelemetry.Log.AfterConnect(SocketError);
- }
+ SocketsTelemetry.Log.AfterConnect(SocketError);
break;
}
}
@@ -578,9 +574,9 @@ internal void StartOperationAccept()
}
}
- internal void StartOperationConnect(MultipleConnectAsync? multipleConnect, bool userSocket)
+ internal void StartOperationConnect(bool saeaMultiConnectCancelable, bool userSocket)
{
- _multipleConnect = multipleConnect;
+ _multipleConnectCancellation = saeaMultiConnectCancelable ? new CancellationTokenSource() : null;
_connectSocket = null;
_userSocket = userSocket;
}
@@ -589,11 +585,11 @@ internal void CancelConnectAsync()
{
if (_operating == InProgress && _completedOperation == SocketAsyncOperation.Connect)
{
- MultipleConnectAsync? multipleConnect = _multipleConnect;
- if (multipleConnect != null)
+ CancellationTokenSource? multipleConnectCancellation = _multipleConnectCancellation;
+ if (multipleConnectCancellation != null)
{
// If a multiple connect is in progress, abort it.
- multipleConnect.Cancel();
+ multipleConnectCancellation.Cancel();
}
else
{
@@ -635,15 +631,6 @@ internal void FinishOperationSyncFailure(SocketError socketError, int bytesTrans
Complete();
}
- internal void FinishConnectByNameSyncFailure(Exception exception, int bytesTransferred, SocketFlags flags)
- {
- SetResults(exception, bytesTransferred, flags);
-
- _currentSocket?.UpdateStatusAfterSocketError(_socketError);
-
- Complete();
- }
-
internal void FinishOperationAsyncFailure(SocketError socketError, int bytesTransferred, SocketFlags flags)
{
ExecutionContext? context = _context; // store context before it's cleared as part of finishing the operation
@@ -660,48 +647,212 @@ internal void FinishOperationAsyncFailure(SocketError socketError, int bytesTran
}
}
- internal void FinishConnectByNameAsyncFailure(Exception exception, int bytesTransferred, SocketFlags flags)
+ /// Performs an asynchronous connect involving a DNS lookup.
+ /// The DNS end point to which to connect.
+ /// The SocketType to use to construct new sockets, if necessary.
+ /// The ProtocolType to use to construct new sockets, if necessary.
+ /// true if the operation is pending; otherwise, false if it's already completed.
+ internal bool DnsConnectAsync(DnsEndPoint endPoint, SocketType socketType, ProtocolType protocolType)
{
- ExecutionContext? context = _context; // store context before it's cleared as part of finishing the operation
+ Debug.Assert(endPoint.AddressFamily == AddressFamily.Unspecified ||
+ endPoint.AddressFamily == AddressFamily.InterNetwork ||
+ endPoint.AddressFamily == AddressFamily.InterNetworkV6);
+
+ CancellationToken cancellationToken = _multipleConnectCancellation?.Token ?? default;
+
+ // In .NET 5 and earlier, the APM implementation allowed for synchronous exceptions from this to propagate
+ // synchronously. This call is made here rather than in the Core async method below to preserve that behavior.
+ Task addressesTask = Dns.GetHostAddressesAsync(endPoint.Host, endPoint.AddressFamily, cancellationToken);
+
+ // Initialize the internal event args instance. It needs to be initialized with `this` instance's buffer
+ // so that it may be used as part of receives during a connect.
+ // TODO https://github.com/dotnet/runtime/issues/30252#issuecomment-511231055: Try to avoid this extra level of SAEA.
+ var internalArgs = new MultiConnectSocketAsyncEventArgs();
+ internalArgs.CopyBufferFrom(this);
+
+ // Delegate to the actual implementation. The returned Task is unused and ignored, as the whole body is surrounded
+ // by a try/catch. Thus we ignore the result. We avoid an "async void" method so as to skip the implicit SynchronizationContext
+ // interactions async void methods entail.
+ _ = Core(internalArgs, addressesTask, endPoint.Port, socketType, protocolType, cancellationToken);
+
+ // Determine whether the async operation already completed and stored the results into `this`.
+ // If we reached this point and the operation hasn't yet stored the results, then it's considered
+ // pending. If by the time we get here it has stored the results, it's considered completed.
+ // The callback won't invoke the Completed event if it gets there first.
+ return internalArgs.ReachedCoordinationPointFirst();
+
+ async Task Core(MultiConnectSocketAsyncEventArgs internalArgs, Task addressesTask, int port, SocketType socketType, ProtocolType protocolType, CancellationToken cancellationToken)
+ {
+ Socket? tempSocketIPv4 = null, tempSocketIPv6 = null;
+ Exception? caughtException = null;
+ try
+ {
+ // Try each address in turn. We store the last error received, such that if we fail to connect to all addresses,
+ // we can use the last error to represent the entire operation.
+ SocketError lastError = SocketError.NoData;
+ foreach (IPAddress address in await addressesTask.ConfigureAwait(false))
+ {
+ Socket? attemptSocket = null;
+ if (_currentSocket != null)
+ {
+ // If this SocketAsyncEventArgs was configured with a socket, then use it.
+ // If that instance doesn't support this address, move on to the next.
+ if (!_currentSocket.CanTryAddressFamily(address.AddressFamily))
+ {
+ continue;
+ }
- FinishConnectByNameSyncFailure(exception, bytesTransferred, flags);
+ attemptSocket = _currentSocket;
+ }
+ else
+ {
+ // If this SocketAsyncEventArgs doesn't have a socket, then we need to create a temporary one, which we do
+ // based on this address' address family (and then reuse for subsequent addresses for the same family).
+ if (address.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ attemptSocket = tempSocketIPv6 ??= (Socket.OSSupportsIPv6 ? new Socket(AddressFamily.InterNetworkV6, socketType, protocolType) : null);
+ }
+ else if (address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ attemptSocket = tempSocketIPv4 ??= (Socket.OSSupportsIPv4 ? new Socket(AddressFamily.InterNetwork, socketType, protocolType) : null);
+ }
- if (context == null)
- {
- OnCompletedInternal();
- }
- else
- {
- ExecutionContext.Run(context, s_executionCallback, this);
- }
- }
+ // If we were unable to get a socket to use for this address, move on to the next address.
+ if (attemptSocket is null)
+ {
+ continue;
+ }
+ }
- internal void FinishWrapperConnectSyncSuccess(Socket? connectSocket, int bytesTransferred, SocketFlags flags)
- {
- SetResults(SocketError.Success, bytesTransferred, flags);
- _currentSocket = connectSocket;
- _connectSocket = connectSocket;
+ // Reset the socket if necessary to support another connect. This is necessary on Unix in particular where
+ // the same socket handle can't be used for another connect, so we swap in a new handle under the covers if
+ // possible. We do this not just for the 2nd+ address but also for the first in case the Socket was already
+ // used for a connection attempt outside of this call.
+ attemptSocket.ReplaceHandleIfNecessaryAfterFailedConnect();
- if (SocketsTelemetry.Log.IsEnabled()) LogBytesTransferEvents(connectSocket?.SocketType, SocketAsyncOperation.Connect, bytesTransferred);
+ // Reconfigure the internal event args for the new address.
+ if (internalArgs.RemoteEndPoint is IPEndPoint existing)
+ {
+ existing.Address = address;
+ Debug.Assert(existing.Port == port);
+ }
+ else
+ {
+ internalArgs.RemoteEndPoint = new IPEndPoint(address, port);
+ }
- // Complete the operation and raise the event.
- Complete();
- }
+ // Issue the connect. If it pends, wait for it to complete.
+ if (attemptSocket.ConnectAsync(internalArgs))
+ {
+ using (cancellationToken.UnsafeRegister(s => Socket.CancelConnectAsync((SocketAsyncEventArgs)s!), internalArgs))
+ {
+ await new ValueTask(internalArgs, internalArgs.Version).ConfigureAwait(false);
+ }
+ }
- internal void FinishWrapperConnectAsyncSuccess(Socket? connectSocket, int bytesTransferred, SocketFlags flags)
- {
- ExecutionContext? context = _context; // store context before it's cleared as part of completing the operation
+ // If it completed successfully, we're done; cleanup will be handled by the finally.
+ if (internalArgs.SocketError == SocketError.Success)
+ {
+ return;
+ }
+
+ // If the operation was canceled, simulate the appropriate SocketError.
+ if (cancellationToken.IsCancellationRequested)
+ {
+ throw new SocketException((int)SocketError.OperationAborted);
+ }
- FinishWrapperConnectSyncSuccess(connectSocket, bytesTransferred, flags);
+ lastError = internalArgs.SocketError;
+ internalArgs.Reset();
+ }
- if (context == null)
- {
- OnCompletedInternal();
+ caughtException = new SocketException((int)lastError);
+ }
+ catch (ObjectDisposedException)
+ {
+ // This can happen if the user closes the socket and is equivalent to a call to CancelConnectAsync.
+ caughtException = new SocketException((int)SocketError.OperationAborted);
+ }
+ catch (Exception exc)
+ {
+ caughtException = exc;
+ }
+ finally
+ {
+ // Close the sockets as needed.
+ if (tempSocketIPv4 != null && !tempSocketIPv4.Connected)
+ {
+ tempSocketIPv4.Dispose();
+ }
+ if (tempSocketIPv6 != null && !tempSocketIPv6.Connected)
+ {
+ tempSocketIPv6.Dispose();
+ }
+ if (_currentSocket != null)
+ {
+ // If the caller-provided socket was a temporary and isn't connected now, or if the failed with an abortive exception,
+ // dispose of the socket.
+ if ((!_userSocket && !_currentSocket.Connected) ||
+ caughtException is OperationCanceledException ||
+ (caughtException is SocketException se && se.SocketErrorCode == SocketError.OperationAborted))
+ {
+ _currentSocket.Dispose();
+ }
+ }
+
+ // Store the results.
+ if (caughtException != null)
+ {
+ SetResults(caughtException, 0, SocketFlags.None);
+ _currentSocket?.UpdateStatusAfterSocketError(_socketError);
+ }
+ else
+ {
+ SetResults(SocketError.Success, internalArgs.BytesTransferred, internalArgs.SocketFlags);
+ _connectSocket = _currentSocket = internalArgs.ConnectSocket!;
+ }
+
+ // Complete the operation.
+ if (SocketsTelemetry.Log.IsEnabled() && !_disableTelemetry) LogBytesTransferEvents(_connectSocket?.SocketType, SocketAsyncOperation.Connect, internalArgs.BytesTransferred);
+ Complete();
+
+ // If the caller is treating this operation as pending, own the completion.
+ internalArgs.Dispose();
+ if (!internalArgs.ReachedCoordinationPointFirst())
+ {
+ // Regardless of _flowExecutionContext, context will have been flown through this async method, as that's part
+ // of what async methods do. As such, we're already on whatever ExecutionContext is the right one to invoke
+ // the completion callback. This method may have even mutated the ExecutionContext, in which case for telemetry
+ // we need those mutations to be surfaced as part of this callback, so that logging performed here sees those
+ // mutations (e.g. to the current Activity).
+ OnCompletedInternal();
+ }
+ }
}
- else
+ }
+
+ private sealed class MultiConnectSocketAsyncEventArgs : SocketAsyncEventArgs, IValueTaskSource
+ {
+ private ManualResetValueTaskSourceCore _mrvtsc;
+ private int _isCompleted;
+
+ public MultiConnectSocketAsyncEventArgs() : base(unsafeSuppressExecutionContextFlow: false)
{
- ExecutionContext.Run(context, s_executionCallback, this);
+ // Instances of this type are an implementation detail of an overarching connect operation.
+ // We don't want to emit telemetry specific to operations on this inner instance.
+ _disableTelemetry = true;
}
+
+ public void GetResult(short token) => _mrvtsc.GetResult(token);
+ public ValueTaskSourceStatus GetStatus(short token) => _mrvtsc.GetStatus(token);
+ public void OnCompleted(Action