pywin32 WaitForMultipleObjects doesn't block when pipe is empty

692 views Asked by At

I have read-only named pipe and would like to block until there is output available (same behaviour as select on socket).

I assumed WaitForMultipleObjects would block if there is no output available, but it does not. What can be done in order to wait efficiently until output is available?

In code below loop is executed 100 times, although PeekNamedPipe returns 0 bytes available to read on every iteration.

import win32security, win32file, win32pipe, win32event, win32con

pipeName = r"\\.\pipe\test-pipe"

saAttr = win32security.SECURITY_ATTRIBUTES()

namedPipe = win32pipe.CreateNamedPipe(
    pipeName,
    win32con.PIPE_ACCESS_DUPLEX | win32con.FILE_FLAG_OVERLAPPED, # open mode
    win32con.PIPE_TYPE_BYTE, # pipe mode
    1, # max instances
    1, # out buffer size
    1, # in buffer size
    0, # timeout
    saAttr)

fileHandle = win32file.CreateFile(pipeName,
                                  win32file.GENERIC_READ,
                                  0, None,
                                  win32file.OPEN_EXISTING,
                                  0, None)

for i in range(100):
    # would expect this to block until data is available
    ret = win32event.WaitForMultipleObjects([fileHandle], 0, win32event.INFINITE)
    print(i, ret)
    size = 1

    buffer, bytesToRead, result = win32pipe.PeekNamedPipe(namedPipe, size)
    print(buffer, bytesToRead, result)
    if bytesToRead > 0:
        res = win32file.ReadFile(namedPipe, size)
1

There are 1 answers

0
stare.in.the.air On

I don't have a way to make WaitForMultipleObjects() block, but an alternative non-blocking way to check, if there is data in the pipe. Surprisingly win32file.GetFileSize() works with named pipes. Microsoft says it doesn't, but it works for me:

import time
import win32file

pipe_path = r'\\.\pipe\asdf'

file_handle = win32file.CreateFile(pipe_path,
                                   win32file.GENERIC_READ | win32file.GENERIC_WRITE,
                                   0, None, win32file.OPEN_EXISTING, 0, None)

while True:
    if win32file.GetFileAttributes(pipe_path) != win32file.FILE_ATTRIBUTE_NORMAL:
        # pipe was closed
        break

    size = win32file.GetFileSize(file_handle)
    if size > 0:
        while size > 0:
            # pipe has data to read
            data = win32file.ReadFile(file_handle, 512)
            print('data received', data)
            size = win32file.GetFileSize(file_handle)
    else:
        time.sleep(1)

The efficiency of the code is debatable. You can see it in action here.

Some more pointers on what I found could be the reason for the unexpected behavior of WaitForMultipleObjects() and WaitForSingleObject():

  • File handles you wish to use with the WaitFor*() methods need to have the SYNCHRONIZE access right (MSDN reference)
    • I'm not sure, but I think this applies only for the creator (server) of named pipe, not the consumer (client).
    • But this is probably not the source of your problem because file handles created with CreateNamedPipe() have the SYNCHRONIZE access right (MSDN reference)
  • The only trustable source I found that uses the win32file.WaitFor*() python wrapper methods is the book "Python Programming On Win32" (on Google Books). But they seem to be waiting for clients connecting, instead of data written to the pipe.