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

Pipe name handling on client side #9

Closed
zbalkan opened this issue Dec 4, 2021 · 9 comments
Closed

Pipe name handling on client side #9

zbalkan opened this issue Dec 4, 2021 · 9 comments

Comments

@zbalkan
Copy link
Contributor

zbalkan commented Dec 4, 2021

The line below adds a number for the given pipe name in specific cases.

var connectionPipeName = $"{PipeName}_{++NextPipeId}";

In my case, a long-running windows service acts as the named pipe server. After a while, the numbering became an issue and I missed that part. I thought it is my code. I realized the issue is by design thanks to pipelist tool of Sysinternals. Then, I checked if it is an OS thin or the library and found the line above.

On the client side, I solved the issue by enumerating the named pipes and picking the first one that starts with the specified name.

Helper class with enumeration method.

public static class PipeHelper
    {
        /// <summary>
        ///     Enumerates the named pipes excluding the ones with illegal names.
        /// </summary>
        /// <remarks>
        ///     Pipes might have illegal characters in path. Seen one from IAR containing < and >.
        ///     The FileSystemEnumerable.MoveNext source code indicates that another call to MoveNext will return
        ///     the next entry.
        ///     Pose a limit in case the underlying implementation changes somehow. This also means that no more
        ///     than 10 (default) pipes with bad names may occur in sequence.
        /// </remarks>
        /// <param name="retryCount"> If there are more illegal named pipes consequently then this param, it stops enumerating. </param>
        /// <see cref="https://stackoverflow.com/a/53432640/5910839"/>
        /// <returns>named pipe names</returns>
        public static IEnumerable<string> EnumeratePipes(int retryCount = 10)
        {
            using var enumerator = Directory.EnumerateFiles(@"\\.\pipe\").GetEnumerator();
            while (MoveNextSafe(enumerator, retryCount))
            {
                yield return enumerator.Current.Replace(@"\\.\pipe\", "");
            }
        }

        private static bool MoveNextSafe(IEnumerator<string> enumerator, int retryCount)
        {
            for (var i = 0; i < retryCount; i++)
            {
                try
                {
                    return enumerator.MoveNext();
                }
                catch (ArgumentException)
                {
                }
            }
            return false;
        }
    }

And on the named pipe client:

...
public class NamedPipeClient : IAsyncDisposable
    {
        private const string PipeName = "MyPipe";

        private readonly PipeClient<PipeMessage> _client;
        ...

        public NamedPipeClient(...)
        {
            var pipe = PipeHelper.EnumeratePipes().First(p => p.StartsWith(PipeName, StringComparison.InvariantCulture));
            if (string.IsNullOrEmpty(pipe))
            {
                throw new InvalidOperationException("No pipe found.");
            }
           ...

You might consider either using it as an example in a sample project or documentation. Or you can add the method in the H.Pipes. It is just an edge case users might face.

@zbalkan
Copy link
Contributor Author

zbalkan commented Dec 4, 2021

This example is oversimplified by the way. I also removed \\.\pipe\ from all names to make .StartsWith() work.

@HavenDV
Copy link
Owner

HavenDV commented Dec 5, 2021

In fact, I want to replace

var connectionPipeName = $"{PipeName}_{++NextPipeId}";

to

var connectionPipeName = $"{PipeName}_{Guid.NewGuid()}";

P.S. I released this change.

@HavenDV
Copy link
Owner

HavenDV commented Dec 5, 2021

The library also contains PipeWatcher.GetActivePipes(), which contains the following code:

public static IReadOnlyCollection<string> GetActivePipes()
{
    return Directory
        .EnumerateFiles(@"\\.\pipe\")
        .Select(path => path.Replace(@"\\.\pipe\", string.Empty))
        .ToList();
}

But I do not recommend using pipe file enumeration in principle, it is very slow.

@HavenDV
Copy link
Owner

HavenDV commented Dec 5, 2021

Regarding the problem with invalid characters, yes, EnumerateFiles returns such an exception when a path contains System.IO.Path.GetInvalidPathChars:

T:System.ArgumentException:
//     path is a zero-length string, contains only white space, or contains invalid
//     characters. You can query for invalid characters by using the System.IO.Path.GetInvalidPathChars
//     method. - or - searchPattern does not contain a valid pattern.

But as I said, EnumerateFiles is generally not good for keeping track of pipes, it is very slow.

@HavenDV
Copy link
Owner

HavenDV commented Dec 5, 2021

At the moment I'm trying to figure out why you need to monitor active pipes at all?

@zbalkan
Copy link
Contributor Author

zbalkan commented Dec 5, 2021

I don't need all the pipes but I just need to have what server uses currently. I see that during operation I can have:

  • pipename
  • pipename_1
  • pipename_2, etc.

But if the client only knows pipename, then client is unable to connect to the server when numbers are there. So, I had to find a pipe that starts with pipename. To solve this, I enumerated all the pipes and pick a suitable one with the code above. However, in case there are multiple pipes at the same time, it takes only the first one and it might be an incorrect one.

@HavenDV
Copy link
Owner

HavenDV commented Dec 5, 2021

Ok, the problem is clear. I will add a Connection.PipeName property that will contain the currently used name.
The principle of the server is that there is a common pipe through which a unique pipeName is transmitted for each client, which is used for a direct connection with the client. Without this trick, only one client would be able to connect to the server. But, in fact, there is already a SingleConnectionPipeServer class that is optimized for a single connection.

@HavenDV
Copy link
Owner

HavenDV commented Dec 5, 2021

I have released a new version. I removed the Connection.Id and Connection.Name properties because the Connection.PipeName will be unique.

@zbalkan
Copy link
Contributor Author

zbalkan commented Dec 5, 2021

That's a huge change. Thank you for quick reply. I'll try to use this one. And also will try SingleConnectionPipeServer, which seems to be the best way for my use case.

@HavenDV HavenDV closed this as completed Dec 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants