【问题标题】:OSError: [Errno 22] Invalid argument in subprocessOSError:[Errno 22] 子进程中的参数无效
【发布时间】:2014-07-04 12:08:37
【问题描述】:

Python 3.3.3 视窗 7

Here is the full stack:
Traceback (most recent call last):
  File "Blah\MyScript.py", line 578, in Call
    output = process.communicate( input=SPACE_KEY, timeout=600 )
  File "C:\Python33\lib\subprocess.py", line 928, in communicate
    stdout, stderr = self._communicate(input, endtime, timeout)
  File "C:\Python33\lib\subprocess.py", line 1202, in _communicate
    self.stdin.write(input)
OSError: [Errno 22] Invalid argument

代码如下所示:

process = subprocess.Popen( arguments,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                universal_newlines=True,
                env=environment )

output = process.communicate( input=SPACE_KEY, timeout=600 )

此代码每天运行数百次而没有问题。但是,如果在同一台机器上运行多个脚本(相同的脚本,但有时来自不同的文件夹),我会收到错误消息。脚本未执行相同的操作(即:当我收到此错误时,另一个脚本未执行子进程)。

subProcess 代码使用许多不同的命令行来引发错误。

那么,有人知道发生了什么吗?解释器是否存在多次执行(在不同进程中)的问题? 通常工作得很好的相同代码,如果解释器运行相同(或非常相似)的脚本,就会出错。但他们通常执行脚本的不同部分。

我不知所措:在 8 核机器上使用单个处理器很烦人。

【问题讨论】:

  • 你运行的是什么操作系统?它看起来像窗户,但由于这似乎是环境问题,您应该在问题中包含它。
  • 检查没有文件名冲突,即脚本不竞争相同的资源。您是否从同一个 Python 脚本启动多个子进程?你使用threading 模块吗?尝试create a minimal complete code example 说明您的问题
  • 是的,这是 Windows 7。没有文件冲突(例如:两个失败的命令是 SUBST 和 REG ADD 不带文件,而另一个正在运行的脚本不这样做)。没有排他锁的资源竞争。每个脚本没有多个子进程。没有线程模块。
  • 这似乎不是问题,但还有一条信息:该脚本(导致错误)实际上是从子进程(同步)运行的。而其他人则不是。并且所有脚本都会启动它们自己的子进程(都是同步的)。
  • 你还遇到这个问题吗?我在调用多个子进程的脚本中看到了相同的消息。

标签: python windows python-3.x subprocess


【解决方案1】:

@eryksun 很好地分析了问题的核心,但我认为他的回答中缺少更大的图景(可能是显而易见的事情)。

那么,有人知道发生了什么吗?

您的代码存在竞争条件。有时您的子进程的行为与您认为的不同。

OSError: [Errno 22] Invalid argument 在您的情况下引发之前 communicate 尝试写入命名管道,subprocess 已在您的父进程和您的子进程的标准输入之间建立。

Popen() 在幕后做了很多工作。在您的情况下,它首先通过_winapi.CreatePipe() 创建三个命名管道(在_get_handles() 中),每个用于stdin/out/err。然后,它使用_winapi.CreateProcess() 生成子进程(在_execute_child() 中)。

_execute_child() 完成清理程序。请记住:所有这些都发生在 Popen() 内。

只有在Popen()返回后,你在父进程中的Python VM才会进行output = process.communicate(input=SPACE_KEY, timeout=600)的调用

鉴于您在一个多核系统上,您的系统有可用的时间片让子进程在您的 Python 解释器仍Popen() 的执行过程中完成一些工作。

也就是说,在_winapi.CreateProcess()(之后子进程执行一些工作)和 Python 尝试通过 communicate() 写入子进程的标准输入(这使得致电 Windows 的 WriteFile,正如 eryksun 很好地解释的那样)。

当您的孩子在那个时间窗口中退出时,您将检索命名错误。

为什么您的子进程早于预期退出?只有你能说出来。显然,它不会总是等待来自标准输入的数据。

【讨论】:

  • Frigg 在评论中提到,它正在启动诸如 reg.exe 之类的程序,而这些程序实际上并不等待从标准输入中读取。否则我将无法重现该错误,即使在调用 process.stdin.write(' ') 之前有相对“长”的延迟。
【解决方案2】:

以前communicate 在写入进程stdin 时只忽略了EPIPE 错误。从 3.3.5 开始,根据 issue 19612,如果孩子已经退出,它也会忽略 EINVAL (22)(参见 Lib/subprocess.py 第 1199 行)。

背景:

process.communiciate 调用process.stdin.write,它调用io.FileIO.write,它在Windows 上调用C 运行时_write,它调用Win32 WriteFile(在这种情况下调用NtWriteFile,它分派到NamedPipe 文件系统, 作为IRP_MJ_WRITEFastIoWrite)。

如果后者失败,它会在线程中设置一个 Windows system error code。在这种情况下,潜在的 Windows 错误可能是 ERROR_NO_DATA (232),因为子进程已经退出。 C 运行时将其映射到 errnoEINVAL (22)。然后由于_write 失败,FileIO.write 根据errno 的当前值提升OSError


附录:

如果 CRT 将 ERROR_NO_DATA 映射到 EPIPE,则根本不会出现问题。 Python 自己的 Windows 错误翻译通常遵循 CRT,但根据 issue 13063,将 ERROR_NO_DATA 映射到 EPIPE (32) 是一个例外。因此,如果孩子已经退出,_winapi.WriteFile 会引发 BrokenPipeError

鉴于子进程已经退出,以下示例复制了EINVAL 错误。它还显示了_winapi.WriteFile(3.3.3 源链接)如何将此错误映射到EPIPE。 IMO,这应该被认为是微软 CRT 中的一个错误。

>>> cmd = 'reg query hkcu'                                                
>>> process = Popen(cmd, stdin=PIPE, stdout=PIPE, universal_newlines=True)
>>> process.stdin.write(' ')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 22] Invalid argument

>>> hstdin = msvcrt.get_osfhandle(process.stdin.fileno())
>>> _winapi.WriteFile(hstdin, b' ')                                       
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
BrokenPipeError: [WinError 232] The pipe is being closed

【讨论】:

  • +1 表示EINVAL issue。问题 13063 似乎无关——在这种情况下是否调用了 winerror_to_errno()?我没有看到 process.stdin.write() 使用 WriteFile()FileIO.write() 调用 write(2)(即使在 Windows 上) -- write(2) 在 Windows 上是否通过 WriteFile 实现?
  • @J.F.Sebastian,在第一段中我说FileIO.write 基于errno 引发,所以它当然没有使用Python 的Windows 错误翻译。第 2 段和示例显示了子进程模块 希望 C 运行时在这种情况下为 errno 设置的内容,以便 communicate 会看到 EPIPE 错误,它是 已经 设计为忽略。相反,问题 19612 的解决方案引入了对 EINVAL 的特殊处理,用于检查子进程是否已退出。
  • @J.F.Sebastian,通过测试,我确认os.write 也会引发EINVAL。在posixmodule.c中,它调用posix_error,它又调用PyErr_SetFromErrno
  • @J.F.Sebastian, WriteFile 从底层 NT status code 设置 Win32 最后一个错误,这最终取决于文件系统在 IO_STATUS_BLOCK 中设置的内容。 ntdll!NtWriteFile 上的断点显示状态代码为 STATUS_PIPE_CLOSING (0xC00000B1)。通过 ctypes 可以看到windll.ntdll.RtlNtStatusToDosError(0xC00000B1) == 232。断开管道的其他错误状态代码是 STATUS_PIPE_DISCONNECTED (Win32 ERROR_PIPE_NOT_CONNECTED) 和 STATUS_PIPE_LISTENING (Win32 ERROR_PIPE_LISTENING)。
  • @J.F.Sebastian,我只能得到ERROR_BROKEN_PIPE 用于从process.stdout 管道读取。在这种情况下,在ntdll!NtReadFile 上设置断点显示状态代码为STATUS_PIPE_BROKEN。所以我认为WriteFile 文档要么令人失望地不完整,要么就是完全错误。看起来有人同时编写了文档的该部分和 ReadFile 的相应部分,但实际上并没有检查 WriteFile 的错误是否不同。
【解决方案3】:

对于您正在使用的操作系统,命令 (args) 可能不正确。尝试清理它们(检查/仅通过允许的字符)。

【讨论】:

  • 它每天工作数百次,并且 args 并不是真正动态的,它只是一个元组,因此封闭函数可以获取实际的 args。没有理由进行清理,我可以完全控制脚本中的命令。
猜你喜欢
  • 2019-04-27
  • 1970-01-01
  • 2018-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-10
  • 2021-02-10
  • 2021-11-06
相关资源
最近更新 更多