mirror of https://github.com/python/cpython
gh-113117: Support posix_spawn in subprocess.Popen with close_fds=True (#113118)
Add support for `os.POSIX_SPAWN_CLOSEFROM` and `posix_spawn_file_actions_addclosefrom_np` and have the `subprocess` module use them when available. This means `posix_spawn` can now be used in the default `close_fds=True` situation on many platforms. Co-authored-by: Gregory P. Smith [Google LLC] <greg@krypto.org>
This commit is contained in:
parent
32d87a8899
commit
2b93f52242
|
@ -4601,10 +4601,17 @@ written in Python, such as a mail server's external command delivery program.
|
||||||
|
|
||||||
Performs ``os.dup2(fd, new_fd)``.
|
Performs ``os.dup2(fd, new_fd)``.
|
||||||
|
|
||||||
|
.. data:: POSIX_SPAWN_CLOSEFROM
|
||||||
|
|
||||||
|
(``os.POSIX_SPAWN_CLOSEFROM``, *fd*)
|
||||||
|
|
||||||
|
Performs ``os.closerange(fd, INF)``.
|
||||||
|
|
||||||
These tuples correspond to the C library
|
These tuples correspond to the C library
|
||||||
:c:func:`!posix_spawn_file_actions_addopen`,
|
:c:func:`!posix_spawn_file_actions_addopen`,
|
||||||
:c:func:`!posix_spawn_file_actions_addclose`, and
|
:c:func:`!posix_spawn_file_actions_addclose`,
|
||||||
:c:func:`!posix_spawn_file_actions_adddup2` API calls used to prepare
|
:c:func:`!posix_spawn_file_actions_adddup2`, and
|
||||||
|
:c:func:`!posix_spawn_file_actions_addclosefrom_np` API calls used to prepare
|
||||||
for the :c:func:`!posix_spawn` call itself.
|
for the :c:func:`!posix_spawn` call itself.
|
||||||
|
|
||||||
The *setpgroup* argument will set the process group of the child to the value
|
The *setpgroup* argument will set the process group of the child to the value
|
||||||
|
@ -4649,6 +4656,10 @@ written in Python, such as a mail server's external command delivery program.
|
||||||
.. versionchanged:: 3.13
|
.. versionchanged:: 3.13
|
||||||
*env* parameter accepts ``None``.
|
*env* parameter accepts ``None``.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
``os.POSIX_SPAWN_CLOSEFROM`` is available on platforms where
|
||||||
|
:c:func:`!posix_spawn_file_actions_addclosefrom_np` exists.
|
||||||
|
|
||||||
.. availability:: Unix, not Emscripten, not WASI.
|
.. availability:: Unix, not Emscripten, not WASI.
|
||||||
|
|
||||||
.. function:: posix_spawnp(path, argv, env, *, file_actions=None, \
|
.. function:: posix_spawnp(path, argv, env, *, file_actions=None, \
|
||||||
|
|
|
@ -293,6 +293,11 @@ os
|
||||||
process use the current process environment.
|
process use the current process environment.
|
||||||
(Contributed by Jakub Kulik in :gh:`113119`.)
|
(Contributed by Jakub Kulik in :gh:`113119`.)
|
||||||
|
|
||||||
|
* :func:`os.posix_spawn` gains an :attr:`os.POSIX_SPAWN_CLOSEFROM` attribute for
|
||||||
|
use in ``file_actions=`` on platforms that support
|
||||||
|
:c:func:`!posix_spawn_file_actions_addclosefrom_np`.
|
||||||
|
(Contributed by Jakub Kulik in :gh:`113117`.)
|
||||||
|
|
||||||
pathlib
|
pathlib
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -342,6 +347,21 @@ sqlite3
|
||||||
object is not :meth:`closed <sqlite3.Connection.close>` explicitly.
|
object is not :meth:`closed <sqlite3.Connection.close>` explicitly.
|
||||||
(Contributed by Erlend E. Aasland in :gh:`105539`.)
|
(Contributed by Erlend E. Aasland in :gh:`105539`.)
|
||||||
|
|
||||||
|
subprocess
|
||||||
|
----------
|
||||||
|
|
||||||
|
* The :mod:`subprocess` module now uses the :func:`os.posix_spawn` function in
|
||||||
|
more situations. Notably in the default case of ``close_fds=True`` on more
|
||||||
|
recent versions of platforms including Linux, FreeBSD, and Solaris where the
|
||||||
|
C library provides :c:func:`!posix_spawn_file_actions_addclosefrom_np`.
|
||||||
|
On Linux this should perform similar to our existing Linux :c:func:`!vfork`
|
||||||
|
based code. A private control knob :attr:`!subprocess._USE_POSIX_SPAWN` can
|
||||||
|
be set to ``False`` if you need to force :mod:`subprocess` not to ever use
|
||||||
|
:func:`os.posix_spawn`. Please report your reason and platform details in
|
||||||
|
the CPython issue tracker if you set this so that we can improve our API
|
||||||
|
selection logic for everyone.
|
||||||
|
(Contributed by Jakub Kulik in :gh:`113117`.)
|
||||||
|
|
||||||
sys
|
sys
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -415,6 +435,12 @@ Optimizations
|
||||||
* :func:`textwrap.indent` is now ~30% faster than before for large input.
|
* :func:`textwrap.indent` is now ~30% faster than before for large input.
|
||||||
(Contributed by Inada Naoki in :gh:`107369`.)
|
(Contributed by Inada Naoki in :gh:`107369`.)
|
||||||
|
|
||||||
|
* The :mod:`subprocess` module uses :func:`os.posix_spawn` in more situations
|
||||||
|
including the default where ``close_fds=True`` on many modern platforms. This
|
||||||
|
should provide a noteworthy performance increase launching processes on
|
||||||
|
FreeBSD and Solaris. See the ``subprocess`` section above for details.
|
||||||
|
(Contributed by Jakub Kulik in :gh:`113117`.)
|
||||||
|
|
||||||
|
|
||||||
Deprecated
|
Deprecated
|
||||||
==========
|
==========
|
||||||
|
|
|
@ -748,6 +748,7 @@ def _use_posix_spawn():
|
||||||
# guarantee the given libc/syscall API will be used.
|
# guarantee the given libc/syscall API will be used.
|
||||||
_USE_POSIX_SPAWN = _use_posix_spawn()
|
_USE_POSIX_SPAWN = _use_posix_spawn()
|
||||||
_USE_VFORK = True
|
_USE_VFORK = True
|
||||||
|
_HAVE_POSIX_SPAWN_CLOSEFROM = hasattr(os, 'POSIX_SPAWN_CLOSEFROM')
|
||||||
|
|
||||||
|
|
||||||
class Popen:
|
class Popen:
|
||||||
|
@ -1751,7 +1752,7 @@ class Popen:
|
||||||
errread, errwrite)
|
errread, errwrite)
|
||||||
|
|
||||||
|
|
||||||
def _posix_spawn(self, args, executable, env, restore_signals,
|
def _posix_spawn(self, args, executable, env, restore_signals, close_fds,
|
||||||
p2cread, p2cwrite,
|
p2cread, p2cwrite,
|
||||||
c2pread, c2pwrite,
|
c2pread, c2pwrite,
|
||||||
errread, errwrite):
|
errread, errwrite):
|
||||||
|
@ -1777,6 +1778,10 @@ class Popen:
|
||||||
):
|
):
|
||||||
if fd != -1:
|
if fd != -1:
|
||||||
file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2))
|
file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2))
|
||||||
|
|
||||||
|
if close_fds:
|
||||||
|
file_actions.append((os.POSIX_SPAWN_CLOSEFROM, 3))
|
||||||
|
|
||||||
if file_actions:
|
if file_actions:
|
||||||
kwargs['file_actions'] = file_actions
|
kwargs['file_actions'] = file_actions
|
||||||
|
|
||||||
|
@ -1824,7 +1829,7 @@ class Popen:
|
||||||
if (_USE_POSIX_SPAWN
|
if (_USE_POSIX_SPAWN
|
||||||
and os.path.dirname(executable)
|
and os.path.dirname(executable)
|
||||||
and preexec_fn is None
|
and preexec_fn is None
|
||||||
and not close_fds
|
and (not close_fds or _HAVE_POSIX_SPAWN_CLOSEFROM)
|
||||||
and not pass_fds
|
and not pass_fds
|
||||||
and cwd is None
|
and cwd is None
|
||||||
and (p2cread == -1 or p2cread > 2)
|
and (p2cread == -1 or p2cread > 2)
|
||||||
|
@ -1836,7 +1841,7 @@ class Popen:
|
||||||
and gids is None
|
and gids is None
|
||||||
and uid is None
|
and uid is None
|
||||||
and umask < 0):
|
and umask < 0):
|
||||||
self._posix_spawn(args, executable, env, restore_signals,
|
self._posix_spawn(args, executable, env, restore_signals, close_fds,
|
||||||
p2cread, p2cwrite,
|
p2cread, p2cwrite,
|
||||||
c2pread, c2pwrite,
|
c2pread, c2pwrite,
|
||||||
errread, errwrite)
|
errread, errwrite)
|
||||||
|
|
|
@ -3348,6 +3348,7 @@ class POSIXProcessTestCase(BaseTestCase):
|
||||||
@unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"),
|
@unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"),
|
||||||
"vfork() not enabled by configure.")
|
"vfork() not enabled by configure.")
|
||||||
@mock.patch("subprocess._fork_exec")
|
@mock.patch("subprocess._fork_exec")
|
||||||
|
@mock.patch("subprocess._USE_POSIX_SPAWN", new=False)
|
||||||
def test__use_vfork(self, mock_fork_exec):
|
def test__use_vfork(self, mock_fork_exec):
|
||||||
self.assertTrue(subprocess._USE_VFORK) # The default value regardless.
|
self.assertTrue(subprocess._USE_VFORK) # The default value regardless.
|
||||||
mock_fork_exec.side_effect = RuntimeError("just testing args")
|
mock_fork_exec.side_effect = RuntimeError("just testing args")
|
||||||
|
@ -3366,9 +3367,13 @@ class POSIXProcessTestCase(BaseTestCase):
|
||||||
@unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"),
|
@unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"),
|
||||||
"vfork() not enabled by configure.")
|
"vfork() not enabled by configure.")
|
||||||
@unittest.skipIf(sys.platform != "linux", "Linux only, requires strace.")
|
@unittest.skipIf(sys.platform != "linux", "Linux only, requires strace.")
|
||||||
|
@mock.patch("subprocess._USE_POSIX_SPAWN", new=False)
|
||||||
def test_vfork_used_when_expected(self):
|
def test_vfork_used_when_expected(self):
|
||||||
# This is a performance regression test to ensure we default to using
|
# This is a performance regression test to ensure we default to using
|
||||||
# vfork() when possible.
|
# vfork() when possible.
|
||||||
|
# Technically this test could pass when posix_spawn is used as well
|
||||||
|
# because libc tends to implement that internally using vfork. But
|
||||||
|
# that'd just be testing a libc+kernel implementation detail.
|
||||||
strace_binary = "/usr/bin/strace"
|
strace_binary = "/usr/bin/strace"
|
||||||
# The only system calls we are interested in.
|
# The only system calls we are interested in.
|
||||||
strace_filter = "--trace=clone,clone2,clone3,fork,vfork,exit,exit_group"
|
strace_filter = "--trace=clone,clone2,clone3,fork,vfork,exit,exit_group"
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
The :mod:`subprocess` module can now use the :func:`os.posix_spawn` function
|
||||||
|
with ``close_fds=True`` on platforms where
|
||||||
|
``posix_spawn_file_actions_addclosefrom_np`` is available.
|
||||||
|
Patch by Jakub Kulik.
|
|
@ -6834,6 +6834,9 @@ enum posix_spawn_file_actions_identifier {
|
||||||
POSIX_SPAWN_OPEN,
|
POSIX_SPAWN_OPEN,
|
||||||
POSIX_SPAWN_CLOSE,
|
POSIX_SPAWN_CLOSE,
|
||||||
POSIX_SPAWN_DUP2
|
POSIX_SPAWN_DUP2
|
||||||
|
#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP
|
||||||
|
,POSIX_SPAWN_CLOSEFROM
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM)
|
#if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM)
|
||||||
|
@ -7074,6 +7077,24 @@ parse_file_actions(PyObject *file_actions,
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP
|
||||||
|
case POSIX_SPAWN_CLOSEFROM: {
|
||||||
|
int fd;
|
||||||
|
if (!PyArg_ParseTuple(file_action, "Oi"
|
||||||
|
";A closefrom file_action tuple must have 2 elements",
|
||||||
|
&tag_obj, &fd))
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
errno = posix_spawn_file_actions_addclosefrom_np(file_actionsp,
|
||||||
|
fd);
|
||||||
|
if (errno) {
|
||||||
|
posix_error();
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
default: {
|
default: {
|
||||||
PyErr_SetString(PyExc_TypeError,
|
PyErr_SetString(PyExc_TypeError,
|
||||||
"Unknown file_actions identifier");
|
"Unknown file_actions identifier");
|
||||||
|
@ -16774,6 +16795,9 @@ all_ins(PyObject *m)
|
||||||
if (PyModule_AddIntConstant(m, "POSIX_SPAWN_OPEN", POSIX_SPAWN_OPEN)) return -1;
|
if (PyModule_AddIntConstant(m, "POSIX_SPAWN_OPEN", POSIX_SPAWN_OPEN)) return -1;
|
||||||
if (PyModule_AddIntConstant(m, "POSIX_SPAWN_CLOSE", POSIX_SPAWN_CLOSE)) return -1;
|
if (PyModule_AddIntConstant(m, "POSIX_SPAWN_CLOSE", POSIX_SPAWN_CLOSE)) return -1;
|
||||||
if (PyModule_AddIntConstant(m, "POSIX_SPAWN_DUP2", POSIX_SPAWN_DUP2)) return -1;
|
if (PyModule_AddIntConstant(m, "POSIX_SPAWN_DUP2", POSIX_SPAWN_DUP2)) return -1;
|
||||||
|
#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP
|
||||||
|
if (PyModule_AddIntMacro(m, POSIX_SPAWN_CLOSEFROM)) return -1;
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(HAVE_SPAWNV) || defined (HAVE_RTPSPAWN)
|
#if defined(HAVE_SPAWNV) || defined (HAVE_RTPSPAWN)
|
||||||
|
|
|
@ -17779,6 +17779,12 @@ if test "x$ac_cv_func_posix_spawnp" = xyes
|
||||||
then :
|
then :
|
||||||
printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h
|
printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h
|
||||||
|
|
||||||
|
fi
|
||||||
|
ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addclosefrom_np" "ac_cv_func_posix_spawn_file_actions_addclosefrom_np"
|
||||||
|
if test "x$ac_cv_func_posix_spawn_file_actions_addclosefrom_np" = xyes
|
||||||
|
then :
|
||||||
|
printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP 1" >>confdefs.h
|
||||||
|
|
||||||
fi
|
fi
|
||||||
ac_fn_c_check_func "$LINENO" "pread" "ac_cv_func_pread"
|
ac_fn_c_check_func "$LINENO" "pread" "ac_cv_func_pread"
|
||||||
if test "x$ac_cv_func_pread" = xyes
|
if test "x$ac_cv_func_pread" = xyes
|
||||||
|
|
|
@ -4757,6 +4757,7 @@ AC_CHECK_FUNCS([ \
|
||||||
lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \
|
lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \
|
||||||
mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
|
mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
|
||||||
pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \
|
pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \
|
||||||
|
posix_spawn_file_actions_addclosefrom_np \
|
||||||
pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \
|
pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \
|
||||||
pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
|
pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
|
||||||
rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \
|
rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \
|
||||||
|
|
|
@ -905,6 +905,10 @@
|
||||||
/* Define to 1 if you have the `posix_spawnp' function. */
|
/* Define to 1 if you have the `posix_spawnp' function. */
|
||||||
#undef HAVE_POSIX_SPAWNP
|
#undef HAVE_POSIX_SPAWNP
|
||||||
|
|
||||||
|
/* Define to 1 if you have the `posix_spawn_file_actions_addclosefrom_np'
|
||||||
|
function. */
|
||||||
|
#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP
|
||||||
|
|
||||||
/* Define to 1 if you have the `pread' function. */
|
/* Define to 1 if you have the `pread' function. */
|
||||||
#undef HAVE_PREAD
|
#undef HAVE_PREAD
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue