Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9daecf3

Browse files
bpo-35537: subprocess uses os.posix_spawn in some cases (pythonGH-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 <[email protected]>
1 parent a37f524 commit 9daecf3

File tree

4 files changed

+99
-0
lines changed

4 files changed

+99
-0
lines changed

Doc/whatsnew/3.8.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,15 @@ xml
275275
Optimizations
276276
=============
277277

278+
* The :mod:`subprocess` module can now use the :func:`os.posix_spawn` function
279+
in some cases for better performance. Currently, it is only used on macOS
280+
and Linux (using glibc 2.24 or newer) if all these conditions are met:
281+
282+
* *close_fds* is false;
283+
* *preexec_fn*, *pass_fds*, *cwd*, *stdin*, *stdout*, *stderr* and
284+
*start_new_session* parameters are not set;
285+
* the *executable* path contains a directory.
286+
278287
* :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`,
279288
:func:`shutil.copytree` and :func:`shutil.move` use platform-specific
280289
"fast-copy" syscalls on Linux, macOS and Solaris in order to copy the file

Lib/subprocess.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,57 @@ def getoutput(cmd):
606606
return getstatusoutput(cmd)[1]
607607

608608

609+
def _use_posix_spawn():
610+
"""Check is posix_spawn() can be used for subprocess.
611+
612+
subprocess requires a posix_spawn() implementation that reports properly
613+
errors to the parent process, set errno on the following failures:
614+
615+
* process attribute actions failed
616+
* file actions failed
617+
* exec() failed
618+
619+
Prefer an implementation which can use vfork in some cases for best
620+
performances.
621+
"""
622+
if _mswindows or not hasattr(os, 'posix_spawn'):
623+
# os.posix_spawn() is not available
624+
return False
625+
626+
if sys.platform == 'darwin':
627+
# posix_spawn() is a syscall on macOS and properly reports errors
628+
return True
629+
630+
# Check libc name and runtime libc version
631+
try:
632+
ver = os.confstr('CS_GNU_LIBC_VERSION')
633+
# parse 'glibc 2.28' as ('glibc', (2, 28))
634+
parts = ver.split(maxsplit=1)
635+
if len(parts) != 2:
636+
# reject unknown format
637+
raise ValueError
638+
libc = parts[0]
639+
version = tuple(map(int, parts[1].split('.')))
640+
641+
if sys.platform == 'linux' and libc == 'glibc' and version >= (2, 24):
642+
# glibc 2.24 has a new Linux posix_spawn implementation using vfork
643+
# which properly reports errors to the parent process.
644+
return True
645+
# Note: Don't use the POSIX implementation of glibc because it doesn't
646+
# use vfork (even if glibc 2.26 added a pipe to properly report errors
647+
# to the parent process).
648+
except (AttributeError, ValueError, OSError):
649+
# os.confstr() or CS_GNU_LIBC_VERSION value not available
650+
pass
651+
652+
# By default, consider that the implementation does not properly report
653+
# errors.
654+
return False
655+
656+
657+
_USE_POSIX_SPAWN = _use_posix_spawn()
658+
659+
609660
class Popen(object):
610661
""" Execute a child program in a new process.
611662
@@ -1390,6 +1441,23 @@ def _get_handles(self, stdin, stdout, stderr):
13901441
errread, errwrite)
13911442

13921443

1444+
def _posix_spawn(self, args, executable, env, restore_signals):
1445+
"""Execute program using os.posix_spawn()."""
1446+
if env is None:
1447+
env = os.environ
1448+
1449+
kwargs = {}
1450+
if restore_signals:
1451+
# See _Py_RestoreSignals() in Python/pylifecycle.c
1452+
sigset = []
1453+
for signame in ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ'):
1454+
signum = getattr(signal, signame, None)
1455+
if signum is not None:
1456+
sigset.append(signum)
1457+
kwargs['setsigdef'] = sigset
1458+
1459+
self.pid = os.posix_spawn(executable, args, env, **kwargs)
1460+
13931461
def _execute_child(self, args, executable, preexec_fn, close_fds,
13941462
pass_fds, cwd, env,
13951463
startupinfo, creationflags, shell,
@@ -1414,6 +1482,20 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
14141482

14151483
if executable is None:
14161484
executable = args[0]
1485+
1486+
if (_USE_POSIX_SPAWN
1487+
and os.path.dirname(executable)
1488+
and preexec_fn is None
1489+
and not close_fds
1490+
and not pass_fds
1491+
and cwd is None
1492+
and p2cread == p2cwrite == -1
1493+
and c2pread == c2pwrite == -1
1494+
and errread == errwrite == -1
1495+
and not start_new_session):
1496+
self._posix_spawn(args, executable, env, restore_signals)
1497+
return
1498+
14171499
orig_executable = executable
14181500

14191501
# For transferring possible exec failure from child to parent.

Lib/test/pythoninfo.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,11 @@ def collect_get_config(info_add):
610610
info_add('%s[%s]' % (prefix, key), repr(config[key]))
611611

612612

613+
def collect_subprocess(info_add):
614+
import subprocess
615+
copy_attributes(info_add, subprocess, 'subprocess.%s', ('_USE_POSIX_SPAWN',))
616+
617+
613618
def collect_info(info):
614619
error = False
615620
info_add = info.add
@@ -639,6 +644,7 @@ def collect_info(info):
639644
collect_cc,
640645
collect_gdbm,
641646
collect_get_config,
647+
collect_subprocess,
642648

643649
# Collecting from tests should be last as they have side effects.
644650
collect_test_socket,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The :mod:`subprocess` module can now use the :func:`os.posix_spawn` function in
2+
some cases for better performance.

0 commit comments

Comments
 (0)