为啥 os.path.exists() 会阻止 Windows 命名管道连接?
Posted
技术标签:
【中文标题】为啥 os.path.exists() 会阻止 Windows 命名管道连接?【英文标题】:Why does os.path.exists() stop windows named pipes from connecting?为什么 os.path.exists() 会阻止 Windows 命名管道连接? 【发布时间】: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
通配符过滤器。这只能通过 NTAPI NtQueryDirectoryFile
: FileFullDirectoryInformation
获得。
【参考方案1】:
os.path.exists
委托给 os.stat
,在 Windows 上,os.stat
尝试委托给 open 文件:
hFile = CreateFileW(path, access, 0, NULL, OPEN_EXISTING, flags, NULL);
然后它关闭文件。此时客户端无法重新打开管道,除非服务器端首先调用DisconnectNamedPipe
,这在这段代码中不会发生。此外,当服务器端尝试从管道读取时,它正在从os.stat
的连接尝试中读取,这会关闭管道而不写入任何数据。
我认为这是一个错误。 os.stat
不应该有这样的副作用。
【讨论】:
以上是关于为啥 os.path.exists() 会阻止 Windows 命名管道连接?的主要内容,如果未能解决你的问题,请参考以下文章
os模块 os.stat('path/filename') os.path.dirname(path) os.path.exists(path) os.pat
“é”来自哪个字符集? (Python:带“é”的文件名,如何使用 os.path.exists , filecmp.cmp, shutil.move?)