Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ private bool TryConnect(int _ /* timeout */)
// either succeeding immediately if the server is listening or failing
// immediately if it isn't. The only delay will be between the time the server
// has called Bind and Listen, with the latter immediately following the former.

// If the socket file doesn't exist yet, skip the socket allocation and
// Connect call that would throw a SocketException. Only ENOENT (not found)
// is short-circuited; permission errors and other conditions still reach
// socket.Connect so they surface their specific exceptions.
// TOCTOU note: the file could appear between this check and the next retry
// iteration, but ConnectInternal's polling loop handles that naturally.
if (Interop.Sys.Stat(_normalizedPipePath!, out Interop.Sys.FileStatus _) != 0 &&
Comment thread
jeffhandley marked this conversation as resolved.
Interop.Sys.GetLastErrorInfo().Error == Interop.Error.ENOENT)
{
return false;
}

var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
SafePipeHandle? clientHandle = null;
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DotNet.RemoteExecutor;
using Xunit;

namespace System.IO.Pipes.Tests
Expand Down Expand Up @@ -632,6 +633,45 @@ public void ClientConnect_Throws_Timeout_When_Pipe_Not_Found()
}
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[PlatformSpecific(TestPlatforms.AnyUnix)]
[SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")]
public void ClientConnect_PipeNotFound_DoesNotFloodFirstChanceExceptions()
{
RemoteExecutor.Invoke(() =>
{
string pipeName = PipeStreamConformanceTests.GetUniquePipeName();
int socketExceptionCount = 0;

EventHandler<System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs> handler = (sender, args) =>
{
if (args.Exception is Net.Sockets.SocketException)
{
Interlocked.Increment(ref socketExceptionCount);
}
};

AppDomain.CurrentDomain.FirstChanceException += handler;
try
{
using (NamedPipeClientStream client = new NamedPipeClientStream(pipeName))
{
Assert.Throws<TimeoutException>(() => client.Connect(500));
}
}
finally
{
AppDomain.CurrentDomain.FirstChanceException -= handler;
}

// Before the fix, connecting to a non-existent pipe for 500ms would
// throw hundreds or thousands of SocketExceptions internally.
// With the Stat-based guard, zero SocketExceptions should be thrown
// when the pipe file never exists. Allow a small margin for races.
Assert.InRange(socketExceptionCount, 0, 5);
}).Dispose();
}

[Theory]
[MemberData(nameof(GetCancellationTokens))]
[SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")]
Expand Down