Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve NamedPipeClientStream.Connect edge cases handling #65553

Merged
merged 5 commits into from
Mar 16, 2022

Conversation

adamsitnik
Copy link
Member

Based on my understanding of the two following quotes from the docs:

https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew

If the CreateNamedPipe function was not successfully called on the server prior to this operation, a pipe will not exist and CreateFile will fail with ERROR_FILE_NOT_FOUND

https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-waitnamedpipew

If no instances of the specified named pipe exist, the WaitNamedPipe function returns immediately, regardless of the time-out value.

There is no point of calling WaitNamedPipe right after CreateFileW failed with ERROR_FILE_NOT_FOUND (we are going to get the same error, unless the pipe was created in the meantime). Imo it's better to return false and let the re-try logic retry in a moment.

The C++ example also calls WaitNamedPipe only when CreateFileW failed with ERROR_PIPE_BUSY.

Based on

A subsequent CreateFile call to the pipe can fail, because the instance was closed by the server or opened by another client.

I've extended the condition to not fail on ERROR_FILE_NOT_FOUND as suggested by @vonzshik in #65434

fixes #65434

@adamsitnik adamsitnik added this to the 7.0.0 milestone Feb 18, 2022
@ghost ghost assigned adamsitnik Feb 18, 2022
@ghost
Copy link

ghost commented Feb 18, 2022

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Based on my understanding of the two following quotes from the docs:

https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew

If the CreateNamedPipe function was not successfully called on the server prior to this operation, a pipe will not exist and CreateFile will fail with ERROR_FILE_NOT_FOUND

https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-waitnamedpipew

If no instances of the specified named pipe exist, the WaitNamedPipe function returns immediately, regardless of the time-out value.

There is no point of calling WaitNamedPipe right after CreateFileW failed with ERROR_FILE_NOT_FOUND (we are going to get the same error, unless the pipe was created in the meantime). Imo it's better to return false and let the re-try logic retry in a moment.

The C++ example also calls WaitNamedPipe only when CreateFileW failed with ERROR_PIPE_BUSY.

Based on

A subsequent CreateFile call to the pipe can fail, because the instance was closed by the server or opened by another client.

I've extended the condition to not fail on ERROR_FILE_NOT_FOUND as suggested by @vonzshik in #65434

fixes #65434

Author: adamsitnik
Assignees: -
Labels:

area-System.IO

Milestone: 7.0.0

@adamsitnik
Copy link
Member Author

FWIW I've tried to create a nice unit test based on repro provided in #65434 but I've failed (it would not be reliable). So I've used repro provided by @vonzshik to test the fix.

Here is what I had:

static async Task Repro(CancellationToken cancellationToken)
{
    const int ClientCount = 10;
    const int ServerCount = 3;

    int timeout = (int)TimeSpan.FromSeconds(0.03).TotalMilliseconds;
    Barrier barrier = new Barrier(ClientCount + ServerCount);

    var clientTasks = Enumerable.Range(0, ClientCount).Select(i => Task.Run(async () =>
    {
        await using var client = new NamedPipeClientStream("foo");

        try
        {
            Task connect = client.ConnectAsync(timeout, cancellationToken);

            while (connect.Status != TaskStatus.Running) ;

            barrier.SignalAndWait();

            await connect;
        }
        catch (TimeoutException)
        {
            return;
        }

        await client.WriteAsync(new byte[1], cancellationToken);
    }, cancellationToken)).ToArray();

    var serverTasks = Enumerable.Range(0, ServerCount).Select(i => Task.Run(async () =>
    {
        barrier.SignalAndWait(); // wait until clients start to connect

        await using var server = new NamedPipeServerStream("foo", PipeDirection.InOut, ServerCount);
        await server.WaitForConnectionAsync(cancellationToken);
        await server.ReadAsync(new byte[1], cancellationToken);
    }, cancellationToken)).ToArray();

    await Task.WhenAll(clientTasks);
    await Task.WhenAll(serverTasks);
}

@adamsitnik adamsitnik requested a review from jozkee March 14, 2022 09:55
@adamsitnik
Copy link
Member Author

@stephentoub @jozkee PTAL

@adamsitnik adamsitnik requested a review from stephentoub March 16, 2022 12:42
Copy link
Member

@stephentoub stephentoub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Test?

Co-authored-by: Stephen Toub <stoub@microsoft.com>
@adamsitnik
Copy link
Member Author

Test?

#65553 (comment)

@adamsitnik adamsitnik merged commit 5032dc5 into dotnet:main Mar 16, 2022
@adamsitnik adamsitnik deleted the issue65434 branch March 16, 2022 21:46
radekdoulik pushed a commit to radekdoulik/runtime that referenced this pull request Mar 30, 2022
* don't try to call WaitNamedPipeW if we know that no instances exist and the sys-call will quit immediately

* remove code duplication

Co-authored-by: Stephen Toub <stoub@microsoft.com>
@ghost ghost locked as resolved and limited conversation to collaborators Apr 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

NamedPipeClientStream sometimes throws FileNotFoundException
2 participants