8

The WaitNamedPipe function allows a pipe client application to synchronously wait for an available connection on a named pipe server. You then call CreateFile to open the pipe as a client. Pseudocode:

// loop works around race condition with WaitNamedPipe and CreateFile
HANDLE hPipe;
while (true) {
    if (WaitNamedPipe says connection is ready) {
        hPipe = CreateFile(...);
        if (hPipe ok or last error is NOT pipe busy) {
            break; // hPipe is valid or last error is set
        }
    } else {
        break; // WaitNamedPipe failed
    }
}

The problem is that these are all blocking, synchronous calls. What is a good way to do this asynchronously? I can't seem to find an API that uses overlapped I/O to do this, for example. For example, for pipe servers the ConnectNamedPipe function provides an lpOverlapped parameters allowing for a server to asynchronously wait for a client. The pipe server can then call WaitForMultipleObjects and wait for the I/O operation to complete, or any other event to be signaled (for example, an event signaling the thread to cancel pending I/O and terminate).

The only way I can think of is to call WaitNamedPipe in a loop with a short, finite timeout and check other signals if it times out. Alternatively, in a loop call CreateFile, check other signals, and then call Sleep with a short delay (or WaitNamedPipe). For example:

HANDLE hPipe;
while (true) {
    hPipe = CreateFile(...);
    if (hPipe not valid and pipe is busy) {
        // sleep 100 milliseconds; alternatively, call WaitNamedPipe with timeout
        Sleep(100);
        // TODO: check other signals here to see if we should abort I/O
    } else
        break;
}

But this method stinks to high heaven in my opinion. If a pipe isn't available for awhile, the thread continues to run - sucking up CPU, using power, requiring memory pages to remain in RAM, etc. In my mind, a thread that relies on Sleep or short timeouts does not perform well and is a sign of sloppy multi-threaded programming.

But what's the alternative in this case?

James Johnston
  • 9,264
  • 9
  • 48
  • 76
  • 1
    One obvious solution is to put the call to WaitNamedPipe in a separate thread. IIRC, several of the asynchronous IO functions actually use threading under the hood, so this isn't as inefficient as it sounds. – Harry Johnston Mar 26 '12 at 23:48
  • Tell us more about the race condition being worked around by this code. The MS documentation sample client code says to call CreateFile first, and only call WaitNamedPipe if Create fails with Pipe Busy. Do it in a loop, with an appropriate time-out on the Wait. That has always worked in my experience. The error only occurs when two clients go for one pipe, but it isn't a race. One client connects, and the other waits and re-tries which is the same thing that happens with your Sleep example, but cleaner. – Mark Taylor Mar 27 '12 at 00:29
  • 1
    @Mark: it's in the documentation. When WaitNamedPipe succeeds, CreateFile might still fail because another thread jumped in first. That's a race condition: two or more threads are racing to open the pipe. Putting the loop in works around the race condition, hence the comment in the OPs code. That's not the problem he's asking us to solve, though. – Harry Johnston Mar 27 '12 at 03:30
  • Expanded answer. Basically Poster's `CreateFile`/`Sleep`loop is a perfectly good solution to this problem - nothing further is required. – Ben Mar 27 '12 at 07:47
  • @Ben: well ... it's a perfectly adequate solution, and the best available. I wouldn't say it's perfectly good. Even on the server end, the absence of an APC-based equivalent to ConnectNamedPipe is a pain if your code is otherwise based on an APC message loop. – Harry Johnston Mar 27 '12 at 20:33
  • @HarryJohnston, ConnectNamedPipe works overlapped, so this is not an issue for the server. – Ben Mar 27 '12 at 21:00
  • @Ben: it has an overlapped mode, but no APC mode. You have to wait on an event object. – Harry Johnston Mar 27 '12 at 21:20
  • @HarryJohnston, That is no barrier since you can code your APC loop around WaitForMultipleObjectsEx (for example). There is no actual *problem* here. – Ben Mar 27 '12 at 21:49
  • 1
    @Ben: No, it's not a problem, but it's a pain. It makes your code harder to maintain, because the modularity is broken. – Harry Johnston Mar 27 '12 at 22:35

3 Answers3

6

WaitNamedPipe is completely useless, and will just use all the cpu if you specify a timeout and there's no server waiting for it.

Just call CreateFile over and over with a Sleep like you're doing, and move it to other threads as you see appropriate. There is no API alternative.

The only "benefit" WaitNamedPipe provides is if you want to know if you can connect to a named pipe but you explicitly don't want to actually open a connection. It's junk.

If you really want to be thorough, your only options are

  • Ensure that whatever program is opening the named pipe is always calling CreateNamedPipe again immediately after it's named pipe is connected to.
  • Have your program actually check if that program is running.
  • If your intent is really not to have additional connections, still call CreateNamedPipe, and when someone connects, tell them to go away until they're waited a given amount of time, the close the pipe.
fcrick
  • 484
  • 5
  • 12
  • I'm guessing it only uses all the CPU if nothing else is competing? It should at least be yielding the CPU once per clock tick. This actually makes sense - it means that the underlying protocol doesn't provide any way to queue up connections, which explains why there's no async version. It's the same with TCP connections. If the server end isn't listening, there's no way to say "Oh, OK then, call me back when you're free", you just have to retry periodically. – Harry Johnston Apr 01 '12 at 01:29
  • It's just neglect by Microsoft. When you call `WaitNamedPipe` with a timeout, one of your cpus will be at %100 until it returns - it's called a livelock - other things will be allowed to run, but there will be no idle cycles at all on that cpu. I suspect they'll remedy it within a decade or two. – fcrick Apr 01 '12 at 20:54
  • I wound up just using a limited timeout. – James Johnston Apr 10 '12 at 21:20
  • Note that if the named pipe you're connecting to is on the local machine, using a timeout of 0 will always give you back a correct answer. You would only ever require a timeout if you were trying to determine whether a named pipe was available (and not connect) on a remote host. – fcrick Apr 12 '12 at 20:40
  • @fcrick: The behavior he is describing (with `WaitNamedPipe` consuming extra CPU cycles while waiting) evidently has been fixed. I didn't observe it on Windows 10. But I agree, this API seems to be useless. Especially when it does not provide any means to stop it while it's in the waiting mode. – ahmd0 Nov 15 '17 at 01:04
2

Why can't the server just create more pipes? The performance hit in the scenario you describe isn't a problem if it is rare.

I.e. if there are usually enough pipes to go round what does it matter if you use CreateFile/Sleep instead of WaitForMultipleObjects? The performance hit will not matter.

I also have to question the need for overlapped IO in a client. How many servers is it communicating with at a time? If the answer is less than, say, 10 you could reasonably create a thread per connection.

Basically I am saying I think the reason there is no overlapped WaitforNamedPipe is because there is no reasonable use-case which requires it.

Ben
  • 34,935
  • 6
  • 74
  • 113
  • Clients don't have control over servers, and need to assume the worst. (In this particular case, the server only handles one client at a time, but I think even if a server handled multiple clients at a time, clients should still be prepared for a full server.) – James Johnston Mar 26 '12 at 23:48
  • @JamesJohnston: are you not writing the server code? Named pipes are mostly used by clients and servers written together. – Harry Johnston Mar 27 '12 at 03:41
  • @Ben The use case that requires an overlapped IO version of WaitNamedPipe is where the client wants to do something while waiting, but putting the wait in its own thread is not an option. That's hardly an uncommon use case! – Ian Goldby Jan 25 '18 at 15:57
  • @IanGoldby you don't explain why putting the wait in its own thread is not an option. Threads are not *that* expensive. – Ben Jan 25 '18 at 16:21
0

You can open the pipe file system at \\.\pipe\ and then use DeviceIoControl to send FSCTL_PIPE_WAIT.

avakar
  • 32,009
  • 9
  • 68
  • 103
  • I think it works for remote pipes as well, just open `\\machine\pipe` instead. – avakar May 16 '20 at 12:57
  • Hi, I can't get this method to work , DeviceIoControl fails with ERROR_INVALID_FUNCTION. Any ideas what could I be doing wrong ? – Sergey Jun 27 '21 at 00:15