summaryrefslogtreecommitdiffstats
path: root/Lib/subprocess.py
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@redhat.com>2019-01-15 23:02:35 (GMT)
committerGitHub <noreply@github.com>2019-01-15 23:02:35 (GMT)
commit9daecf37a571e98aaf43a387bcc9e41a7132f477 (patch)
tree845a37230f2a9d6eed12a4fc84727394ad323d68 /Lib/subprocess.py
parenta37f52436f9aa4b9292878b72f3ff1480e2606c3 (diff)
downloadcpython-9daecf37a571e98aaf43a387bcc9e41a7132f477.zip
cpython-9daecf37a571e98aaf43a387bcc9e41a7132f477.tar.gz
cpython-9daecf37a571e98aaf43a387bcc9e41a7132f477.tar.bz2
bpo-35537: subprocess uses os.posix_spawn in some cases (GH-11452)
The subprocess module can now use the os.posix_spawn() function in some cases for better performance. Currently, it is only used on macOS and Linux (using glibc 2.24 or newer) if all these conditions are met: * executable path contains a directory * close_fds=False * preexec_fn, pass_fds, cwd, stdin, stdout, stderr and start_new_session parameters are not set Co-authored-by: Joannah Nanjekye <nanjekyejoannah@gmail.com>
Diffstat (limited to 'Lib/subprocess.py')
-rw-r--r--Lib/subprocess.py82
1 files changed, 82 insertions, 0 deletions
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index 6966176..b94575b 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -606,6 +606,57 @@ def getoutput(cmd):
return getstatusoutput(cmd)[1]
+def _use_posix_spawn():
+ """Check is posix_spawn() can be used for subprocess.
+
+ subprocess requires a posix_spawn() implementation that reports properly
+ errors to the parent process, set errno on the following failures:
+
+ * process attribute actions failed
+ * file actions failed
+ * exec() failed
+
+ Prefer an implementation which can use vfork in some cases for best
+ performances.
+ """
+ if _mswindows or not hasattr(os, 'posix_spawn'):
+ # os.posix_spawn() is not available
+ return False
+
+ if sys.platform == 'darwin':
+ # posix_spawn() is a syscall on macOS and properly reports errors
+ return True
+
+ # Check libc name and runtime libc version
+ try:
+ ver = os.confstr('CS_GNU_LIBC_VERSION')
+ # parse 'glibc 2.28' as ('glibc', (2, 28))
+ parts = ver.split(maxsplit=1)
+ if len(parts) != 2:
+ # reject unknown format
+ raise ValueError
+ libc = parts[0]
+ version = tuple(map(int, parts[1].split('.')))
+
+ if sys.platform == 'linux' and libc == 'glibc' and version >= (2, 24):
+ # glibc 2.24 has a new Linux posix_spawn implementation using vfork
+ # which properly reports errors to the parent process.
+ return True
+ # Note: Don't use the POSIX implementation of glibc because it doesn't
+ # use vfork (even if glibc 2.26 added a pipe to properly report errors
+ # to the parent process).
+ except (AttributeError, ValueError, OSError):
+ # os.confstr() or CS_GNU_LIBC_VERSION value not available
+ pass
+
+ # By default, consider that the implementation does not properly report
+ # errors.
+ return False
+
+
+_USE_POSIX_SPAWN = _use_posix_spawn()
+
+
class Popen(object):
""" Execute a child program in a new process.
@@ -1390,6 +1441,23 @@ class Popen(object):
errread, errwrite)
+ def _posix_spawn(self, args, executable, env, restore_signals):
+ """Execute program using os.posix_spawn()."""
+ if env is None:
+ env = os.environ
+
+ kwargs = {}
+ if restore_signals:
+ # See _Py_RestoreSignals() in Python/pylifecycle.c
+ sigset = []
+ for signame in ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ'):
+ signum = getattr(signal, signame, None)
+ if signum is not None:
+ sigset.append(signum)
+ kwargs['setsigdef'] = sigset
+
+ self.pid = os.posix_spawn(executable, args, env, **kwargs)
+
def _execute_child(self, args, executable, preexec_fn, close_fds,
pass_fds, cwd, env,
startupinfo, creationflags, shell,
@@ -1414,6 +1482,20 @@ class Popen(object):
if executable is None:
executable = args[0]
+
+ if (_USE_POSIX_SPAWN
+ and os.path.dirname(executable)
+ and preexec_fn is None
+ and not close_fds
+ and not pass_fds
+ and cwd is None
+ and p2cread == p2cwrite == -1
+ and c2pread == c2pwrite == -1
+ and errread == errwrite == -1
+ and not start_new_session):
+ self._posix_spawn(args, executable, env, restore_signals)
+ return
+
orig_executable = executable
# For transferring possible exec failure from child to parent.