OSError: [Errno 22] Недопустимый аргумент в подпроцессе

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 )

Этот код работает сотни раз в день без проблем. Но если на одном компьютере работает более одного script (тот же script, но иногда из разных папок), я получаю сообщение об ошибке. Сценарии не выполняют одно и то же (т.е.: другой script не выполняет подпроцесс, когда я получаю эту ошибку).

Код подпроцесса вызывает ошибку со многими различными командами, поданными на нее.

Итак, у кого-то есть идея о том, что происходит? У интерпретатора есть проблема с множественным исполнением (в разных процессах)? Тот же код, который нормально работает отлично, дерьмо, если интерпретатор работает с тем же (или очень похожим) скриптом. Но они обычно выполняют разные части script.

Я в недоумении: использование одного процессора на 8-ядерном компьютере раздражает.

Ответ 1

@eryksun очень хорошо проанализировал суть проблемы, но я думаю, что более большая картина (очевидные вещи, возможно) отсутствует в его ответе.

Итак, у кого-то есть идея относительно того, что происходит?

Ваш код страдает от состояния гонки. Иногда ваш дочерний процесс не ведет себя так, как вы думаете, что он ведет себя.

OSError: [Errno 22] Invalid argument возникает в вашем случае, когда ребенок выходит до того, как communicate пытается записать именованный канал, который subprocess установил между вашим родителем и stdin вашего дочернего процесса.

Popen() делает многое под капотом. В вашем случае сначала создается три именованных канала (в _get_handles()), через _winapi.CreatePipe(), по одному для каждого из stdin/out/err. Затем он запускает дочерний процесс (в _execute_child()), используя _winapi.CreateProcess().

_execute_child() заканчивается процедурами очистки. Помните: все это происходит в Popen().

Только после того, как Popen() вернется, ваша Python VM в родительском процессе вот-вот продолжит вызов output = process.communicate(input=SPACE_KEY, timeout=600)

Учитывая, что вы работаете в многоядерной системе, ваша система имеет временные фрагменты, позволяющие дочернему процессу выполнять некоторую работу, пока ваш интерпретатор Python все еще находится в процессе выполнения Popen().

То есть узкое окно времени между _winapi.CreateProcess() (после чего дочерний процесс выполняет некоторую работу), и Python пытается записать дочерний stdin через communicate() (что делает вызовите Windows 'WriteFile, как хорошо объяснил eryksun).

Когда ваш ребенок выйдет в окне ,, вы получите именованную ошибку.

Почему ваш дочерний процесс завершается раньше, чем ожидалось? Только вы можете сказать. Очевидно, что он не всегда ждет данных, поступающих из stdin.

Ответ 2

Ранее communicate игнорировал ошибку EPIPE при записи в процесс stdin. Начиная с 3.3.5, за issue 19612, он также игнорирует EINVAL (22), если ребенок уже вышел (см. Lib/subprocess.py строка 1199).

Фон:

process.communiciate вызывает process.stdin.write, который вызывает io.FileIO.write, который в Windows вызывает C runtime _write, который вызывает Win32 WriteFile (который в этом случае вызывает NtWriteFile, который отправляется в файловую систему NamedPipe, либо как IRP_MJ_WRITE или FastIoWrite).

Если последнее не удается, оно устанавливает в поток поток системный код ошибки. В этом случае основная ошибка 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 to EPIPE (32), Таким образом, если ребенок уже вышел, _winapi.WriteFile вызывает BrokenPipeError.

Следующий пример реплицирует ошибку EINVAL, если дочерний процесс уже завершен. Он также показывает, как _winapi.WriteFile (3.3.3 ссылка источника) вместо этого отобразит эту ошибку в EPIPE. IMO, это должно считаться ошибкой в ​​Microsoft 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

Ответ 3

Команда (args) может быть неправильной для ОС, которую вы используете. Попытайтесь их дезинфицировать (проверьте/пройдите только допустимые символы).