mirror of
https://github.com/python/cpython.git
synced 2025-08-30 21:48:47 +00:00
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:
parent
a37f52436f
commit
9daecf37a5
4 changed files with 99 additions and 0 deletions
|
@ -275,6 +275,15 @@ xml
|
||||||
Optimizations
|
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.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`,
|
||||||
:func:`shutil.copytree` and :func:`shutil.move` use platform-specific
|
:func:`shutil.copytree` and :func:`shutil.move` use platform-specific
|
||||||
"fast-copy" syscalls on Linux, macOS and Solaris in order to copy the file
|
"fast-copy" syscalls on Linux, macOS and Solaris in order to copy the file
|
||||||
|
|
|
@ -606,6 +606,57 @@ def getoutput(cmd):
|
||||||
return getstatusoutput(cmd)[1]
|
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):
|
class Popen(object):
|
||||||
""" Execute a child program in a new process.
|
""" Execute a child program in a new process.
|
||||||
|
|
||||||
|
@ -1390,6 +1441,23 @@ class Popen(object):
|
||||||
errread, errwrite)
|
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,
|
def _execute_child(self, args, executable, preexec_fn, close_fds,
|
||||||
pass_fds, cwd, env,
|
pass_fds, cwd, env,
|
||||||
startupinfo, creationflags, shell,
|
startupinfo, creationflags, shell,
|
||||||
|
@ -1414,6 +1482,20 @@ class Popen(object):
|
||||||
|
|
||||||
if executable is None:
|
if executable is None:
|
||||||
executable = args[0]
|
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
|
orig_executable = executable
|
||||||
|
|
||||||
# For transferring possible exec failure from child to parent.
|
# For transferring possible exec failure from child to parent.
|
||||||
|
|
|
@ -610,6 +610,11 @@ def collect_get_config(info_add):
|
||||||
info_add('%s[%s]' % (prefix, key), repr(config[key]))
|
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):
|
def collect_info(info):
|
||||||
error = False
|
error = False
|
||||||
info_add = info.add
|
info_add = info.add
|
||||||
|
@ -639,6 +644,7 @@ def collect_info(info):
|
||||||
collect_cc,
|
collect_cc,
|
||||||
collect_gdbm,
|
collect_gdbm,
|
||||||
collect_get_config,
|
collect_get_config,
|
||||||
|
collect_subprocess,
|
||||||
|
|
||||||
# Collecting from tests should be last as they have side effects.
|
# Collecting from tests should be last as they have side effects.
|
||||||
collect_test_socket,
|
collect_test_socket,
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
The :mod:`subprocess` module can now use the :func:`os.posix_spawn` function in
|
||||||
|
some cases for better performance.
|
Loading…
Add table
Add a link
Reference in a new issue