OSError:[Errno 22]子进程中的参数无效

时间:2014-05-15 20:58:45

标签: python windows python-3.x subprocess

Python 3.3.3 Windows 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核机器上使用单个处理器很烦人。

3 个答案:

答案 0 :(得分:8)

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

那么,任何人都知道发生了什么?

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

OSError: [Errno 22] Invalid argument尝试写入命名管道时,如果孩子退出,则会引发

communicatesubprocess已在您的父管理员之间建立了Popen()你的孩子过程的标准。

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

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

仅在output = process.communicate(input=SPACE_KEY, timeout=600)返回后,父进程中的Python VM即将继续调用Popen()

鉴于您使用的是多核系统,您的系统可以使用时间片让子进程执行某些工作,而您的Python解释器仍然执行_winapi.CreateProcess() 。< / p>

也就是说,communicate()之间有一个狭窄的时间窗口(之后子进程做了一些工作)和Python尝试通过WriteFile写入子进程的stdin (调用Windows {{{1}},正如eryksun很好地解释的那样)。

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

为什么您的子进程比预期更早退出?只有你可以告诉。显然,它总是等待来自stdin的数据。

答案 1 :(得分:5)

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

<强>背景

process.communiciate调用process.stdin.write,调用io.FileIO.write,在Windows上调用C运行时_write,调用Win32 WriteFile(在这种情况下调用发送到NamedPipe文件系统的NtWriteFile,可以是IRP_MJ_WRITEFastIoWrite)。

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


<强>附录:

如果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

答案 2 :(得分:0)

您正在使用的操作系统的命令(args)可能不正确。尝试对它们进行消毒(仅检查/传递允许的字符)。