Commit f6243ac1 authored by Victor Stinner's avatar Victor Stinner Committed by GitHub

bpo-35537: subprocess can use posix_spawn with pipes (GH-11575)

* subprocess.Popen can now also use os.posix_spawn() with pipes,
  but only if pipe file descriptors are greater than 2.
* Fix Popen._posix_spawn(): set '_child_created' attribute to True.
* Add Popen._close_pipe_fds() helper function to factorize the code.
parent ab67281e
...@@ -280,8 +280,8 @@ Optimizations ...@@ -280,8 +280,8 @@ Optimizations
and Linux (using glibc 2.24 or newer) if all these conditions are met: and Linux (using glibc 2.24 or newer) if all these conditions are met:
* *close_fds* is false; * *close_fds* is false;
* *preexec_fn*, *pass_fds*, *cwd*, *stdin*, *stdout*, *stderr* and * *preexec_fn*, *pass_fds*, *cwd* and *start_new_session* parameters
*start_new_session* parameters are not set; are not set;
* the *executable* path contains a directory. * the *executable* path contains a directory.
* :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`, * :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`,
......
...@@ -1066,6 +1066,34 @@ class Popen(object): ...@@ -1066,6 +1066,34 @@ class Popen(object):
pass pass
raise # resume the KeyboardInterrupt raise # resume the KeyboardInterrupt
def _close_pipe_fds(self,
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite):
# self._devnull is not always defined.
devnull_fd = getattr(self, '_devnull', None)
if _mswindows:
if p2cread != -1:
p2cread.Close()
if c2pwrite != -1:
c2pwrite.Close()
if errwrite != -1:
errwrite.Close()
else:
if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd:
os.close(p2cread)
if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:
os.close(c2pwrite)
if errwrite != -1 and errread != -1 and errwrite != devnull_fd:
os.close(errwrite)
if devnull_fd is not None:
os.close(devnull_fd)
# Prevent a double close of these handles/fds from __init__ on error.
self._closed_child_pipe_fds = True
if _mswindows: if _mswindows:
# #
...@@ -1244,17 +1272,9 @@ class Popen(object): ...@@ -1244,17 +1272,9 @@ class Popen(object):
# output pipe are maintained in this process or else the # output pipe are maintained in this process or else the
# pipe will not close when the child process exits and the # pipe will not close when the child process exits and the
# ReadFile will hang. # ReadFile will hang.
if p2cread != -1: self._close_pipe_fds(p2cread, p2cwrite,
p2cread.Close() c2pread, c2pwrite,
if c2pwrite != -1: errread, errwrite)
c2pwrite.Close()
if errwrite != -1:
errwrite.Close()
if hasattr(self, '_devnull'):
os.close(self._devnull)
# Prevent a double close of these handles/fds from __init__
# on error.
self._closed_child_pipe_fds = True
# Retain the process handle, but close the thread handle # Retain the process handle, but close the thread handle
self._child_created = True self._child_created = True
...@@ -1441,7 +1461,10 @@ class Popen(object): ...@@ -1441,7 +1461,10 @@ class Popen(object):
errread, errwrite) errread, errwrite)
def _posix_spawn(self, args, executable, env, restore_signals): def _posix_spawn(self, args, executable, env, restore_signals,
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite):
"""Execute program using os.posix_spawn().""" """Execute program using os.posix_spawn()."""
if env is None: if env is None:
env = os.environ env = os.environ
...@@ -1456,7 +1479,26 @@ class Popen(object): ...@@ -1456,7 +1479,26 @@ class Popen(object):
sigset.append(signum) sigset.append(signum)
kwargs['setsigdef'] = sigset kwargs['setsigdef'] = sigset
file_actions = []
for fd in (p2cwrite, c2pread, errread):
if fd != -1:
file_actions.append((os.POSIX_SPAWN_CLOSE, fd))
for fd, fd2 in (
(p2cread, 0),
(c2pwrite, 1),
(errwrite, 2),
):
if fd != -1:
file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2))
if file_actions:
kwargs['file_actions'] = file_actions
self.pid = os.posix_spawn(executable, args, env, **kwargs) self.pid = os.posix_spawn(executable, args, env, **kwargs)
self._child_created = True
self._close_pipe_fds(p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite)
def _execute_child(self, args, executable, preexec_fn, close_fds, def _execute_child(self, args, executable, preexec_fn, close_fds,
pass_fds, cwd, env, pass_fds, cwd, env,
...@@ -1489,11 +1531,14 @@ class Popen(object): ...@@ -1489,11 +1531,14 @@ class Popen(object):
and not close_fds and not close_fds
and not pass_fds and not pass_fds
and cwd is None and cwd is None
and p2cread == p2cwrite == -1 and (p2cread == -1 or p2cread > 2)
and c2pread == c2pwrite == -1 and (c2pwrite == -1 or c2pwrite > 2)
and errread == errwrite == -1 and (errwrite == -1 or errwrite > 2)
and not start_new_session): and not start_new_session):
self._posix_spawn(args, executable, env, restore_signals) self._posix_spawn(args, executable, env, restore_signals,
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite)
return return
orig_executable = executable orig_executable = executable
...@@ -1548,18 +1593,9 @@ class Popen(object): ...@@ -1548,18 +1593,9 @@ class Popen(object):
# be sure the FD is closed no matter what # be sure the FD is closed no matter what
os.close(errpipe_write) os.close(errpipe_write)
# self._devnull is not always defined. self._close_pipe_fds(p2cread, p2cwrite,
devnull_fd = getattr(self, '_devnull', None) c2pread, c2pwrite,
if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd: errread, errwrite)
os.close(p2cread)
if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:
os.close(c2pwrite)
if errwrite != -1 and errread != -1 and errwrite != devnull_fd:
os.close(errwrite)
if devnull_fd is not None:
os.close(devnull_fd)
# Prevent a double close of these fds from __init__ on error.
self._closed_child_pipe_fds = True
# Wait for exec to fail or succeed; possibly raising an # Wait for exec to fail or succeed; possibly raising an
# exception (limited in size) # exception (limited in size)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment