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>
This commit is contained in:
Victor Stinner 2019-01-16 00:02:35 +01:00 committed by GitHub
parent a37f52436f
commit 9daecf37a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 0 deletions

View File

@ -275,6 +275,15 @@ xml
Optimizations
=============
* The :mod:`subprocess` module can now use the :func:`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:
* *close_fds* is false;
* *preexec_fn*, *pass_fds*, *cwd*, *stdin*, *stdout*, *stderr* and
*start_new_session* parameters are not set;
* the *executable* path contains a directory.
* :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`,
:func:`shutil.copytree` and :func:`shutil.move` use platform-specific
"fast-copy" syscalls on Linux, macOS and Solaris in order to copy the file

View File

@ -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.

View File

@ -610,6 +610,11 @@ def collect_get_config(info_add):
info_add('%s[%s]' % (prefix, key), repr(config[key]))
def collect_subprocess(info_add):
import subprocess
copy_attributes(info_add, subprocess, 'subprocess.%s', ('_USE_POSIX_SPAWN',))
def collect_info(info):
error = False
info_add = info.add
@ -639,6 +644,7 @@ def collect_info(info):
collect_cc,
collect_gdbm,
collect_get_config,
collect_subprocess,
# Collecting from tests should be last as they have side effects.
collect_test_socket,

View File

@ -0,0 +1,2 @@
The :mod:`subprocess` module can now use the :func:`os.posix_spawn` function in
some cases for better performance.