Skip to content

bpo-37410: subprocess closes the process handle when done #14391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed

bpo-37410: subprocess closes the process handle when done #14391

wants to merge 3 commits into from

Conversation

vstinner
Copy link
Member

@vstinner vstinner commented Jun 26, 2019

On Windows, subprocess.Popen now closes the process handle when the
process completes. Previously, it was only closed when the Popen
object was destroyed.

https://bugs.python.org/issue37410

On Windows, subprocess.Popen now closes the process handle when the
process completes. Previously, it was only closed when the Popen
object was destroyed.
@vstinner
Copy link
Member Author

cc @efiop @eryksun @zooba

@vstinner
Copy link
Member Author

See also https://bugs.python.org/issue37380

@vstinner
Copy link
Member Author

cc @gpshead

vstinner added 2 commits June 26, 2019 16:49
* Add _set_returncode() to the Windows implementation
* Rename _handle_exitstatus() to _set_returncode() in the Unix
  implementation
Two newlines between methods.
@vstinner
Copy link
Member Author

Oh, a test now fails on Windows:

ERROR: test_threadsafe_wait (test.test_subprocess.ProcessTestCase)
Issue21291: Popen.wait() needs to be threadsafe for returncode.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\projects\cpython\lib\test\test_subprocess.py", line 1284, in test_threadsafe_wait
    proc.wait(timeout=20)
  File "C:\projects\cpython\lib\subprocess.py", line 1050, in wait
    return self._wait(timeout=timeout)
  File "C:\projects\cpython\lib\subprocess.py", line 1340, in _wait
    exitcode = _winapi.GetExitCodeProcess(self._handle)
TypeError: GetExitCodeProcess() argument must be int, not None

It seems like _wait() is not longer thread-safe with my change... but I'm surprised that it was thread-safe before my change.

The Unix implementation uses a lock to make _wait() thread-safe. Extract:

                    with self._waitpid_lock:
                        if self.returncode is not None:
                            break  # Another thread waited.
                        (pid, sts) = self._try_wait(0)
                        # Check the pid and loop as waitpid has been known to
                        # return 0 even without WNOHANG in odd situations.
                        # http://bugs.python.org/issue14396.
                        if pid == self.pid:
                            self._set_returncode(sts)

Maybe the Windows implementation should also use a lock?

The failing test comes from https://bugs.python.org/issue21291

@eryksun
Copy link
Contributor

eryksun commented Jun 26, 2019

It seems like _wait() is not longer thread-safe with my change... but I'm surprised that it was thread-safe before my change.

Before we could rely on _handle being valid. Now I think we need to acquire a lock before checking and setting the return code. The following is an untested version of _wait that acquires a lock:

    def _wait(self, timeout):
        """Internal implementation of wait() on Windows."""
        if self.returncode is not None:
            return self.returncode

        if timeout is None:
            self._status_lock.acquire()
        else:
            if timeout < 0:
                # Disallow the -1 default timeout value of acquire.
                raise ValueError('timeout value must be positive')
            endtime = _time() + timeout
            if not self._status_lock.acquire(timeout=timeout):
                raise TimeoutExpired(self.args, timeout)
        try:
            if self.returncode is None:
                if timeout is None:
                    remaining_ms = _winapi.INFINITE
                else:
                    remaining = endtime - _time() if timeout != 0 else 0
                    if remaining < 0:
                        raise TimeoutExpired(self.args, timeout)
                    remaining_ms = int(remaining * 1000)
                # API note: returns immediately if remaining_ms is 0.
                result = _winapi.WaitForSingleObject(self._handle,
                    remaining_ms)
                if result == _winapi.WAIT_TIMEOUT:
                    raise TimeoutExpired(self.args, timeout)
                exitcode = _winapi.GetExitCodeProcess(self._handle)
                self._set_returncode(exitcode)
        finally:
            self._status_lock.release()
        return self.returncode

@efiop
Copy link
Contributor

efiop commented Jun 28, 2019

@eryksun It seems like we could re-use _waitpid_lock(it was requested for removal in #14360 (comment)) instead of introducing new _status_lock, right? They seem to have pretty similar meaning to me, but maybe I'm not quite getting the difference.

@vstinner
Copy link
Member Author

vstinner commented Jul 1, 2019

The Unix code path has the same issue and it was already been fixed. I suggest to factorize the code between Windows and Unix: reuse the same lock and the same locking code, but add sub-methods for Windows or Unix specific code.

I plan to write such PR.

@eryksun
Copy link
Contributor

eryksun commented Jul 1, 2019

The Unix code path has the same issue and it was already been fixed. I suggest to factorize the code between Windows and Unix: reuse the same lock and the same locking code, but add sub-methods for Windows or Unix specific code.

The POSIX code uses a busy loop. I see no reason for that in Windows.

@vstinner
Copy link
Member Author

My PR doesn't work: more locking is needed. Code should be shared with Unix, or copied from the Unix implementation, to add locking. I will not be available next weeks, so I prefer to close the issue. I may rewrite it properly later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants