Skip to content

Named pipes hang when written asynchronously w/o async flag #31390

@AArnott

Description

@AArnott

When creating named pipes on .NET or .NET Core, unless I use PipeOptions.Asynchronous, async I/O operations done later end up hanging the I/O operations. Even if I pass in a CancellationToken, these async I/O methods do not cancel when demanded.

IMO the async flag should be simply an optimization -- not something that if set incorrectly causes a hang.
But even if a hang is acceptable, surely the CancellationToken should be honored, no?

Minimal repro: XUnitTestProject5.zip
You'll see the test passes, but if you change PipeOptions used in the repro to None, the test hangs.

Source code:

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

#nullable enable

public class UnitTest1
{
    private readonly CancellationToken TimeoutToken = new CancellationTokenSource(5000).Token;

    private readonly string PipeName = Guid.NewGuid().ToString();
    private const PipeOptions Options = PipeOptions.Asynchronous; // FAILS when set to None
    private const PipeDirection Direction = PipeDirection.InOut;

    [Fact]
    public async Task Test1()
    {
        var serverTask = RunServer();
        using var clientPipe = new NamedPipeClientStream(".", PipeName, Direction, Options);
        await clientPipe.ConnectAsync(this.TimeoutToken);

        Task readTask = ReadBlockAsync(clientPipe, 3, this.TimeoutToken);
        await clientPipe.WriteAsync(new byte[] { 1, 2, 3 }, this.TimeoutToken);
        await clientPipe.FlushAsync(this.TimeoutToken);
        await Task.WhenAll(readTask, serverTask);
    }

    private async Task RunServer()
    {
        using var serverPipe = new NamedPipeServerStream(PipeName, Direction, maxNumberOfServerInstances: 1, PipeTransmissionMode.Byte, Options);
        await serverPipe.WaitForConnectionAsync(this.TimeoutToken);

        Task readTask = ReadBlockAsync(serverPipe, 3, this.TimeoutToken);
        await serverPipe.WriteAsync(new byte[] { 4, 5, 6 }, this.TimeoutToken);
        await serverPipe.FlushAsync(this.TimeoutToken);
        await readTask;
    }

    private static async Task<ReadOnlyMemory<byte>> ReadBlockAsync(Stream stream, int length, CancellationToken cancellationToken)
    {
        byte[] buffer = new byte[length];
        int bytesRead = 0;
        while (bytesRead < length)
        {
            int bytesJustRead = await stream.ReadAsync(buffer.AsMemory(bytesRead), cancellationToken);
            if (bytesJustRead == 0)
            {
                throw new Exception("Lost connection.");
            }

            bytesRead += bytesJustRead;
        }

        return buffer;
    }
}

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions