【发布时间】:2018-07-09 23:36:05
【问题描述】:
似乎使用os.path.exists() 成功测试了名为管道的窗口的存在会阻止管道工作。为什么会这样?
这是成功运行的 windows 命名管道代码:
import time
import multiprocessing as mp
import win32pipe, win32file
PIPENAME = r'\\.\pipe\Foo'
def producer(pipe_name: str):
print('producer')
# if not os.path.exists(pipe_name):
# print(f'No pipe {pipe_name}')
# return
pipe = win32file.CreateFile(pipe_name,
win32file.GENERIC_READ | win32file.GENERIC_WRITE, # dwDesiredAccess
0, # dwShareMode
None, # lpSecurityAttributes
win32file.OPEN_EXISTING, # dwCreationDisposition
0, # dwFlagsAndAttributes
None
)
win32pipe.SetNamedPipeHandleState(pipe, win32pipe.PIPE_READMODE_MESSAGE, None, None)
win32file.WriteFile(pipe, b'foobar')
def receiver(pipe_name: str):
print('receiver')
pipe = win32pipe.CreateNamedPipe(pipe_name,
win32pipe.PIPE_ACCESS_DUPLEX,
win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE | win32pipe.PIPE_WAIT,
1, # nMaxInstances
65536, # nOutBufferSize
65536, # nInBufferSize
0, # 50ms timeout (the default)
None) # securityAttributes
win32pipe.ConnectNamedPipe(pipe)
msg = win32file.ReadFile(pipe, 65536)
print(f'msg: {msg}')
if __name__ == '__main__':
recv_p = mp.Process(target=receiver, args=(PIPENAME,))
prod_p = mp.Process(target=producer, args=(PIPENAME,))
recv_p.start()
time.sleep(0.1)
prod_p.start()
prod_p.join()
recv_p.join()
这按预期工作,接收者打印收到的消息。
但是如果生产者中被注释掉的三个行没有注释,os.path.exists(pipe_name) 调用会以某种方式破坏管道,因此输出变为:
receiver
producer
Process Process-2:
Process Process-1:
Traceback (most recent call last):
File "C:\Users\redacted\AppData\Local\Programs\Python\Python37\lib\multiprocessing\process.py", line 297, in _bootstrap
self.run()
File "C:\Users\redacted\AppData\Local\Programs\Python\Python37\lib\multiprocessing\process.py", line 99, in run
self._target(*self._args, **self._kwargs)
File "C:\git\redacted\named_pipe_mqtt_test.py", line 18, in producer
None
pywintypes.error: (231, 'CreateFile', 'All pipe instances are busy.')
Traceback (most recent call last):
File "C:\Users\redacted\AppData\Local\Programs\Python\Python37\lib\multiprocessing\process.py", line 297, in _bootstrap
self.run()
File "C:\Users\redacted\AppData\Local\Programs\Python\Python37\lib\multiprocessing\process.py", line 99, in run
self._target(*self._args, **self._kwargs)
File "C:\git\redacted\named_pipe_mqtt_test.py", line 35, in receiver
msg = win32file.ReadFile(pipe, 65536)
pywintypes.error: (109, 'ReadFile', 'The pipe has been ended.')
为什么os.path.exists 会破坏窗口命名管道?
我已经排除了 python 多处理库。我在os.path.exists 之后尝试过延迟。
这对我来说不是阻塞问题,但我很好奇。
【问题讨论】:
-
您能否尝试将范围缩小到导致问题的确切原因?首先,我很确定
os.path.exists只是打电话给os.stat,但请检查一下。然后,那一定是调用CreateFileW(FILE_READ_ATTRIBUTES),所以检查是否这样做然后关闭句柄会破坏事情。然后......它可能会在该句柄上调用一些不同的东西,所以你可能需要查看posixmodule.c内部以查看它是否在调用GetFileInformationByHandle或其他东西,但不管它是什么,看看调用是否会破坏事情。以此类推。 -
os.stat确实调用了CreateFileW,查询了一些信息(由于命名管道文件系统只支持非常有限的一组查询而失败)和CloseHandle。管道现在处于关闭状态,服务器端应该检测到错误并调用DisconnectNamedPipe以允许新的连接。 -
调用
FindFirstFileNameW和GetFileAttributes也会导致同样的问题。但这有效:我可以做os.listdir(r'\\.\pipe')并检查管道名称是否存在。似乎很奇怪,没有其他非破坏性方法可以检查命名管道是否存在。 -
在 Windows API 中,使用
FindFirstFile[Ex]和管道名称,例如"\\\\?\\pipe\\Foo"。这在 NT 中通过NtOpenFile打开"\\\\?\\pipe\\"(NPFS 根目录)并通过NtQueryDirectyoryFile[Ex]查询FileName“Foo”来实现。如果未找到FindFirstFile[Ex],则返回INVALID_HANDLE_VALUE。否则通过FindClose关闭查找句柄。如果找到名称,NPFS 会将文件“大小”(nFileSizeLow) 设置为查询时打开的实例数。 -
借助 PyWin32,您可以使用
win32api.FindFiles(r'\\?\pipe\Foo')。如前所述,这将返回当前实例数作为文件大小。如果您想要允许的最大实例数,请使用同时返回 allocation 大小的查询,例如GetFileInformationByHandleEx:FileFullDirectoryInfo(由 NPFS 支持,在 Windows 8+ 中可用)。在他们无限的“智慧”中,他们无法通过此函数传递FileName通配符过滤器。这只能通过 NTAPINtQueryDirectoryFile:FileFullDirectoryInformation获得。
标签: python windows named-pipes pywin32 python-3.7