System.ServiceModel.NetNamedPipe high-concurrency connection attempts are less reliable on net8.0-windows than net48
- Dominant language
- C#
- Stars
- 1.8k
- Forks
- 576
- Avg merge
- 6d 9h
- Merged PRs (30d)
- 2
Description
**Describe the bug**
`System.ServiceModel.NetNamedPipe` appears to handle concurrent connection attempts less reliably when a client application targets `net8.0-windows` than when the same application targets `net48`.
I have uploaded a minimal sample repro solution: https://github.com/fourthprime/NamedPipeConcurrencyRepro
The solution contains:
- `ReproLib` — shared service contract/types
- `ReproService` — hosts the `net.pipe` endpoint
- `ReproClient48` — runs concurrent client calls against the named pipe endpoint (net48)
- `ReproClient80` — runs concurrent client calls against the named pipe endpoint (net8.0-windows)
With the same `ReproService` process running, the `net48` client succeeds reliably on my machine, while the `net8.0-windows` client often fails with a `CommunicationException` during concurrent connection/call attempts.
**To Reproduce**
1. Clone the sample in Windows.
```powershell
git clone https://github.com/fourthprime/NamedPipeConcurrencyRepro.git
```
2. Start `ReproService`.
`ReproService` targets `net48` and hosts the named pipe endpoint used by the client repro. It runs until terminated.
```powershell
dotnet run --project ReproService
```
3. In a separate console window, run `ReproClient48`.
```powershell
dotnet run --project ReproClient48
```
Result on my machine: the program completes successfully. This result is consistent. Output:
```text
CLR dir - C:\Windows\Microsoft.NET\Framework64\v4.0.30319\
Number of processors: 16
Executing 100 requests with max concurrency 100
client repro done
```
4. Run `ReproClient80`.
Example:
```powershell
dotnet run --project ReproClient80
```
Result on my machine: the client often fails with a `CommunicationException`. Output:
```text
CLR dir - c:\pf\dotnet\shared\Microsoft.NETCore.App\8.0.30\
Number of processors: 16
Executing 100 requests with max concurrency 100
UNEXPECTED: timeout on busy named pipe
... (repeated then exception string) ...
```
The failure is not necessarily deterministic on every individual run, but it reproduces consistently for me by rerunning the `net8.0-windows` client.
Full exception/call stack from a failing `net8.0-windows` run:
```text
System.ServiceModel.CommunicationException: Cannot connect to endpoint 'net.pipe://localhost/Repro/IReproService'.
---> System.IO.PipeException: A pipe endpoint exists for 'bfcf524d-162b-4f9f-9a58-806240aeca6d', but the connect failed: The operation has timed out.
--- End of inner exception stack trace ---
at System.ServiceModel.Channels.PipeConnectionInitiator.TryConnect(Uri remoteUri, String resolvedAddress, BackoffTimeoutHelper backoffHelper)
at System.ServiceModel.Channels.PipeConnectionInitiator.ConnectAsync(Uri remoteUri, TimeSpan timeout)
at System.ServiceModel.Channels.BufferedConnectionInitiator.ConnectAsync(Uri uri, TimeSpan timeout)
at System.ServiceModel.Channels.ConnectionPoolHelper.EstablishConnectionAsync(TimeSpan timeout)
at System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.OnOpenAsync(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.OnOpenAsyncInternal(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.System.ServiceModel.IAsyncCommunicationObject.OpenAsync(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.OnOpenAsync(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.OnOpenAsyncInternal(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.System.ServiceModel.IAsyncCommunicationObject.OpenAsync(TimeSpan timeout)
at System.Runtime.TaskHelpers.ToApmEnd(IAsyncResult iar)
at System.ServiceModel.Channels.CommunicationObject.EndOpen(IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.CallOpenOnce.System.ServiceModel.Channels.ServiceChannel.ICallOnce.EndCall(ServiceChannel channel, IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.CallOnceManager.EndCallOnce(IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndEnsureOpened(IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.FinishEnsureOpen(IAsyncResult result, Boolean completedSynchronously)
--- End of stack trace from previous location ---
at System.Runtime.AsyncResult.End[TAsyncResult](IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannelProxy.TaskCreator.<>c__DisplayClass1_0.b__0(IAsyncResult asyncResult)
--- End of stack trace from previous location ---
at Repro.Program.SendAsync(String input)
at Repro.Program.Main(String[] args)
at Repro.Program.(String[] args)
```
For comparison, the equivalent `net48` run using the same service process does not produce this exception (see above output showing it completed successfully).
**Expected behavior**
Concurrent named pipe client connection/call attempts should be at least as reliable on `net8.0-windows` using `System.ServiceModel.NetNamedPipe` as they are when the same repro client targets `net48`, for the same service and test environment.
More specifically, the attached repro should complete successfully for both target frameworks when using the same `ReproService` instance, or the `net8.0-windows` behavior should fail only for a documented/expected reason.
**Screenshots**
N/A.
**Additional context**
I'll include a few details that may help with investigation.
1. Environment where I reproduced this:
- OS: Windows 11 25H2
- .NET SDK: 10.0.203
- .NET Runtime: 8.0.30
- Visual Studio/MSBuild: 18.5.2
2. Note that the repro may change when the debugger is attached (due to a variety of timeout conditions). The most consistent repro is observed when running the program with debugger _detached_ so I recommend doing that for testing.
3. The exception chain observed via break-when-thrown in the debugger:
- `NamedPipeClientStream.ConnectInternal` - throws TimeoutException due to `NamedPipeClientStream.TryConnect` returning false and the timeout expiring.
- `PipeConnectionInitiator.TryConnect` - catches that; throws CommunicationException wrapping PipeException.
4. Based on source for PipeConnectionInitiator and NamedPipeClientStream, this occurs when WaitNamedPipe is called and the pipe is busy, then the supplied 1 ms timeout expires. The divergence from net48 can be gleaned somewhat from decompiled source - net48 appears to elide the call to WaitNamedPipe entirely - if the result is `FILE_NOT_FOUND` or `PIPE_BUSY` while inside the backoff time window then PipeConnectionInitiator retries (presumably as expected) until the pipe is no longer busy.
5. This consistently reproduces when targeting .NET 10 and using latest versions of `System.ServiceModel.Primitives` and `System.ServiceModel.NetNamedPipe` (10.0.652802 as of writing). My repro targets .NET 8 because that is what my company's app targets currently.
Aside, in testing this I've observed substantially increased time spent (~20x) in the WCF connection layer during ReproClient80 runs when compared against ReproClient48. The exception is more impactful so for now I'm deferring the perf concern.
I’m happy to provide additional traces/logs if there is a preferred WCF/System.ServiceModel diagnostic collection method for named pipe connection issues.
Contributor guide
Assessment
This issue has not been assessed yet.