Skip to content

Disposing NamedPipeServerStream does not cancel WaitForConnectionAsync #40674

Closed

Description

Calling Dispose an a NamedPipeServerStream which is currently inside WaitForConnectionAsync does not cancel the underlying listen event when there are multiple NamedPipeServerStream instances active on the same named pipe. This can result in future NamedPipeClientStream.Connect calls going to the disposed NamedPipeServerStream instead of the non-disposed ones.

Consider the example below. This will start two NamedPipeServerStream instances on a single pipe name, then it will dispose the first one and start a client connection to the pipe. The connection will succeed but it connects to the disposed NamedPipeServerStream, not the active one. The stream can then be used without error.

Example code:

using System;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        var pipeName = Guid.NewGuid().ToString();
        using var server1 = CreateServer();
        using var server2 = CreateServer();

        var wait1 = server1.WaitForConnectionAsync();
        server1.Dispose();

        var waitTask = Task.Run(async () =>
        {
            await server2.WaitForConnectionAsync();
            Console.WriteLine("Server 2 got a connection");
        });

        var client = new NamedPipeClientStream(pipeName);
        await client.ConnectAsync();
        Console.WriteLine("Client connected");
        await wait1;
        Console.WriteLine(wait1.Status);

        // Can still use server1 even though it's disposed
        await server1.WriteAsync(new byte[]{ 42 }, 0, 1);
        var buffer = new byte[1];
        await client.ReadAsync(buffer, 0, 1);
        Console.WriteLine(buffer[0]); // Prints 42
       
        Console.WriteLine("At the end");

        NamedPipeServerStream CreateServer() => new NamedPipeServerStream(
            pipeName,
            PipeDirection.InOut,
            maxNumberOfServerInstances: 10,
            PipeTransmissionMode.Byte,
            PipeOptions.Asynchronous);
    }
}

When run on Unix this code will output:

Client connected
RanToCompletion
42
At the end

When run on Windows this code will output:

Server 2 got a connection
Client connected
Unhandled exception. System.IO.IOException: The pipe has been ended.
   at System.IO.Pipes.ConnectionCompletionSource.HandleError(Int32 errorCode)
   at System.IO.Pipes.PipeCompletionSource`1.CompleteCallback(Int32 resultState)
   at System.IO.Pipes.PipeCompletionSource`1.AsyncCallback(UInt32 errorCode, UInt32 numBytes)   at System.IO.Pipes.ConnectionCompletionSource.AsyncCallback(UInt32 errorCode, UInt32 numBytes)
   at System.IO.Pipes.PipeCompletionSource`1.<>c.<.ctor>b__11_0(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOverlapped)
   at System.Threading.ThreadPoolBoundHandleOverlapped.CompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pNativeOverlapped)
--- End of stack trace from previous location ---
   at Program.Main(String[] args) in P:\temp\console\Program.cs:line 26
   at Program.<Main>(String[] args)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions