为啥 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 以允许新的连接。 调用FindFirstFileNameWGetFileAttributes 也会导致同样的问题。但这有效:我可以做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

os模块

如何在Python内部判断某个文件是否存在

“é”来自哪个字符集? (Python:带“é”的文件名,如何使用 os.path.exists , filecmp.cmp, shutil.move?)

python-os

os.path