14

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

The code looks like this:

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 )

This code runs hundreds of times a day without problems. But if more than one script is running on the same machine (the same script, but sometimes from different folders) I get the error. The scripts are not executing the same thing (i.e.: the other script is not executing a subprocess when I get this error).

The subProcess code raises the error with many different command lines fed to it.

So, anyone has an idea as to what is happening? Does the interpreter have a problem with multiple execution (in different processes)? Same code that normally works perfectly fine, craps out if the interpreter is running the same (or very similar) scripts. But they are usually executing different parts of the script.

I'm at a loss: Using a single processor on an 8 core machine is annoying.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
Frigg
  • 369
  • 1
  • 3
  • 12
  • What OS are you running? It looks like windows but since this seems to be environmental you should include that in your question. – b4hand May 15 '14 at 22:20
  • check that there is no filenames conflicts i.e., the scripts do not compete for the same resources. Do you start multiple subprocesses from the same Python script? Do you use `threading` module? Try to [create a minimal complete code example](http://stackoverflow.com/help/mcve) that demostrates your issue – jfs May 16 '14 at 05:46
  • Yes, this is Windows 7. There is no file conflicts (e.g.: two of the failing commands are SUBST and REG ADD which take no file, and the other running script don't do). No resource competition with exclusive lock. No multiple subprocesses per scripts. No threading module. – Frigg May 16 '14 at 13:23
  • It doesn't seem to be the problem, but one more piece of info: That script (causing the errors) is actually run from a subprocess (synchronously). While the others are not. And all scripts start subprocesses of their own (all synchronous). – Frigg May 16 '14 at 13:32
  • Are you still getting this problem? I've seen the same message in a script that calls multiple subprocesses. – trichoplax is on Codidact now Jan 04 '15 at 17:41

3 Answers3

9

Previously communicate only ignored an EPIPE error when writing to the process stdin. Starting with 3.3.5, per issue 19612, it also ignores EINVAL (22) if the child has already exited (see Lib/subprocess.py line 1199).

Background:

process.communiciate calls process.stdin.write, which calls io.FileIO.write, which on Windows calls the C runtime _write, which calls Win32 WriteFile (which in this case calls NtWriteFile, which dispatches to the NamedPipe filesystem, as either an IRP_MJ_WRITE or FastIoWrite).

If the latter fails, it sets a Windows system error code in the thread. In this case the underlying Windows error is probably ERROR_NO_DATA (232) because the child process has already exited. The C runtime maps this to an errno value of EINVAL (22). Then since _write failed, FileIO.write raises OSError based on the current value of errno.


Addendum:

There wouldn't have been a problem at all if the CRT instead mapped ERROR_NO_DATA to EPIPE. Python's own Windows error translation generally follows the CRT's, but per issue 13063, it makes an exception to map ERROR_NO_DATA to EPIPE (32). Thus if the child has already exited, _winapi.WriteFile raises BrokenPipeError.

The following example replicates the EINVAL error given the child process has already exited. It also shows how _winapi.WriteFile (3.3.3 source link) would instead map this error to EPIPE. IMO, this should be considered a bug in Microsoft's 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
Eryk Sun
  • 33,190
  • 5
  • 92
  • 111
  • +1 for the [EINVAL issue](http://bugs.python.org/issue19612). Issue 13063 seems unrelated -- is `winerror_to_errno()` called in this case? I don't see that `process.stdin.write()` uses `WriteFile()`. `FileIO.write()` calls `write(2)` (even on Windows) -- Is `write(2)` implemented via `WriteFile` on Windows? – jfs Jan 20 '15 at 17:24
  • @J.F.Sebastian, in the first paragraph I say that `FileIO.write` raises based on `errno`, so of course it's not using Python's Windows error translation. The 2nd paragraph and example show what the subprocess module would *want* the C runtime to set for `errno` in this case such that `communicate` would see an `EPIPE` error, which it was *already* designed to ignore. Instead the solution for issue 19612 introduced special handling for `EINVAL` that checks whether the child process has exited. – Eryk Sun Jan 20 '15 at 20:15
  • @J.F.Sebastian, the C runtime `_write` does call `WriteFile` in this case and translates the resulting `ERROR_NO_DATA` to an `EINVAL` `errno`. If the file descriptor were in a Unicode mode for a console handle, `_write` would instead call `WriteConsoleW`. Python 3's io module doesn't handle `_O_U16TEXT` mode well, anyway, because `FileIO` doesn't ensure writing in multiples of `sizeof(wchar_t)`, which causes the CRT to bugcheck. – Eryk Sun Jan 20 '15 at 20:23
  • Your 1st paragraph is not clear about what maps windows errors to errno. The 2nd paragraph talks about Python bug that changes how CPython maps windows errors to errno. It creates an impression that CPython maps the error in this case and issue 13063 could have converted the error to EPIPE -- it can't: Windows code sets errno to `22` in this case (Python has nothing to do with this value). It is clear from your comment that Windows and CPython translate ERROR_NO_DATA differently to EINVAL and EPIPE correspondingly and it justifies ignoring EINVAL in `.communicate()`. – jfs Jan 20 '15 at 20:46
  • Unicode + Windows console has nothing to do with this issue too. `subprocess` uses textio (`universal_newlines=True`) only if PIPE is used: no Windows console is involved. – jfs Jan 20 '15 at 20:47
  • @J.F.Sebastian, you're the one who asked whether `_write` calls `WriteFile`. It generally does, but I wanted to cover the case that it doesn't in my comment, and add some tangentially related information that I thought you may find interesting since I had previously talked to you about issue 1602. – Eryk Sun Jan 20 '15 at 21:05
  • exceptional depth. The docs say that `_write` should produce EINVAL if `buffer` is NULL but Python passes non-null `buffer` here (whether child process is alive can't change it) and `WriteFile` is documented to return `ERROR_BROKEN_PIPE` if the read end of the anonymous pipe is closed (if the child process ends; it probably closes its end) and `ERROR_NO_DATA` is not mentioned at all - it is not clear at what level this error occurs. Also, to confirm, `os.write(process.stdin.fileno(), b' ')` raises `EINVAL` on Windows instead of EPIPE in the last code example? – jfs Jan 20 '15 at 22:55
  • 1
    @J.F.Sebastian, from testing I confirm that `os.write` also raises `EINVAL`. In posixmodule.c, it calls [`posix_error`](https://hg.python.org/cpython/file/c3896275c0f6/Modules/posixmodule.c#l1197), which calls `PyErr_SetFromErrno`. – Eryk Sun Jan 21 '15 at 00:13
  • 1
    @J.F.Sebastian, `WriteFile` sets the Win32 last error from the underlying [NT status code](https://msdn.microsoft.com/en-us/library/cc704588.aspx), which ultimately is up to what the filesystem sets in the `IO_STATUS_BLOCK`. A breakpoint on `ntdll!NtWriteFile` shows the status code is `STATUS_PIPE_CLOSING` (0xC00000B1). Via ctypes one can see that `windll.ntdll.RtlNtStatusToDosError(0xC00000B1) == 232`. Other error status codes for a disconnected pipe are `STATUS_PIPE_DISCONNECTED` (Win32 `ERROR_PIPE_NOT_CONNECTED`) and `STATUS_PIPE_LISTENING` (Win32 `ERROR_PIPE_LISTENING`). – Eryk Sun Jan 21 '15 at 00:16
  • 2
    @J.F.Sebastian, I can only get `ERROR_BROKEN_PIPE` for reading from the `process.stdout` pipe. In this case, setting a breakpoint on `ntdll!NtReadFile` shows the status code is `STATUS_PIPE_BROKEN`. So I think the `WriteFile` docs are either disappointingly incomplete or just plain wrong. It looks like someone wrote that section of the docs and the corresponding section for [`ReadFile`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365467%28v=vs.85%29.aspx) at the same time and didn't actually check that the error is different for `WriteFile`. – Eryk Sun Jan 21 '15 at 00:37
8

@eryksun very well analyzed the core of the issue, but I think the bigger picture (the obvious things, probably) is missing in his answer.

So, anyone has an idea as to what is happening?

Your code suffers from a race condition. Sometimes your child process does not behave the way you think it behaves.

OSError: [Errno 22] Invalid argument is raised in your case when the child exits before communicate attempts to write to the named pipe, which subprocess has established between your parent and stdin of your child process.

Popen() does a lot under the hood. In your case, it first creates three named pipes (in _get_handles()), via _winapi.CreatePipe(), one for each of stdin/out/err. Then, it spawns the child process (in _execute_child()), using _winapi.CreateProcess().

_execute_child() finishes with cleanup procedures. Remember: all of this happens within Popen().

Only after Popen() returns, your Python VM in the parent process is about to proceed with the invocation of output = process.communicate(input=SPACE_KEY, timeout=600)

Given that you are on a multi core system, your system has time slices available to let the child process do some work while your Python interpreter is still within execution of Popen().

That is, there is a narrow time window between _winapi.CreateProcess() (after which the child process does some work) and Python's attempt to write to the child's stdin via communicate() (which makes a call to Windows' WriteFile, as eryksun nicely explained).

When your child exits in that time window you retrieve named error.

Why your child process exits earlier than expected? Only you can tell. Obviously, it does not always wait for data coming from stdin.

Dr. Jan-Philip Gehrcke
  • 33,287
  • 14
  • 85
  • 130
  • 1
    Frigg mentioned in a comment that it's starting programs such `reg.exe` that don't actually wait to read from stdin. I wouldn't have been able to reproduce the error otherwise, even with the relatively 'long' delay before calling `process.stdin.write(' ')`. – Eryk Sun Jan 27 '15 at 14:23
0

The command (args) might be incorrect for OS that you're using. Try to sanitize them (check / pass through only allowed characters).

LetMeSOThat4U
  • 6,470
  • 10
  • 53
  • 93
  • 1
    It works hundreds of times a day and args is not really dynamic, it is only a tuple so that the enclosing function can take the actual args. There is no reason to sanitize, I have total control of the command in my script. – Frigg May 20 '14 at 14:34