diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketsTelemetry.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketsTelemetry.cs index 1171961a204351..93baeb37b0a5f2 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketsTelemetry.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketsTelemetry.cs @@ -147,9 +147,16 @@ public void AfterConnect(SocketError error, Activity? activity, string? exceptio long newCount = Interlocked.Decrement(ref _currentOutgoingConnectAttempts); Debug.Assert(newCount >= 0); + // _currentOutgoingConnectAttempts tracks managed Connect calls. A non-blocking Connect call + // can return WouldBlock (Windows) or InProgress (Unix) while the OS connect attempt remains + // pending after this method returns. That later result may be observed by Socket when it + // checks whether the pending non-blocking connect completed, but it is not available to this + // synchronous Connect telemetry callback. Don't report these pending results as failures. + bool connectPending = error is SocketError.WouldBlock or SocketError.InProgress; + if (activity is not null) { - if (error != SocketError.Success) + if (error != SocketError.Success && !connectPending) { activity.SetStatus(ActivityStatusCode.Error); activity.SetTag("error.type", GetErrorType(error)); @@ -163,7 +170,7 @@ public void AfterConnect(SocketError error, Activity? activity, string? exceptio Debug.Assert(exceptionMessage is null); Interlocked.Increment(ref _outgoingConnectionsEstablished); } - else + else if (!connectPending) { ConnectFailed(error, exceptionMessage); } diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs index 69f61fc180a49c..94bc43e74abb40 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/TelemetryTest.cs @@ -158,6 +158,37 @@ static void VerifyTcpConnectActivity(Activity activity, IPEndPoint remoteEndPoin ActivityAssert.HasTag(activity, "network.transport", "tcp"); } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public async Task Connect_NonBlockingPending_ActivityNotMarkedAsError() + { + await RemoteExecutor.Invoke(static () => + { + using Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + server.BindToAnonymousPort(IPAddress.Loopback); + server.Listen(); + + using Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) + { + Blocking = false + }; + + using ActivityRecorder recorder = new ActivityRecorder(ActivitySourceName, ActivityName); + + // A non-blocking connect reports WouldBlock (Windows) or InProgress (Unix) by design while + // the attempt continues in the background. The "socket connect" activity is started and + // stopped synchronously inside Connect, so its final state can be asserted immediately. + SocketException ex = Assert.Throws(() => client.Connect(server.LocalEndPoint)); + Assert.True(ex.SocketErrorCode is SocketError.WouldBlock or SocketError.InProgress, + $"Unexpected SocketError: {ex.SocketErrorCode}"); + + recorder.VerifyActivityRecorded(1); + Activity activity = recorder.LastFinishedActivity; + VerifyTcpConnectActivity(activity, (IPEndPoint)server.LocalEndPoint, ipv6: false); + Assert.Equal(ActivityStatusCode.Unset, activity.Status); + Assert.Null(activity.GetTagItem("error.type")); + }).DisposeAsync(); + } + [OuterLoop("Connection failure takes long on Windows.")] [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] [MemberData(nameof(SocketMethods_WithBools_MemberData))]