Skip to content

Commit 2d54d8e

Browse files
committed
pythongh-104372: Drop the GIL around the vfork() call.
On Linux where the `subprocess` module can use the `vfork` syscall for faster spawning, prevent the parent process from blocking other threads by dropping the GIL while it waits for the vfork'ed child process `exec` outcome. This prevents spawning a binary from a slow filesystem from blocking the rest of the application. Fixes python#104372.
1 parent 2e5d8a9 commit 2d54d8e

File tree

2 files changed

+25
-1
lines changed

2 files changed

+25
-1
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
On Linux where :mod:`subprocess` can use the ``vfork()`` syscall for faster
2+
spawning, prevent the parent process from blocking other threads by dropping
3+
the GIL while it waits for the vfork'ed child process ``exec()`` outcome.
4+
This prevents spawning a binary from a slow filesystem from blocking the
5+
rest of the application.

Modules/_posixsubprocess.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ reset_signal_handlers(const sigset_t *child_sigmask)
559559
* required by POSIX but not supported natively on Linux. Another reason to
560560
* avoid this family of functions is that sharing an address space between
561561
* processes running with different privileges is inherently insecure.
562-
* See bpo-35823 for further discussion and references.
562+
* See https://bugs.python.org/issue35823 for discussion and references.
563563
*
564564
* In some C libraries, setrlimit() has the same thread list/signalling
565565
* behavior since resource limits were per-thread attributes before
@@ -798,15 +798,28 @@ do_fork_exec(char *const exec_array[],
798798
pid_t pid;
799799

800800
#ifdef VFORK_USABLE
801+
PyThreadState *vfork_tstate_save = NULL;
801802
if (child_sigmask) {
802803
/* These are checked by our caller; verify them in debug builds. */
803804
assert(uid == (uid_t)-1);
804805
assert(gid == (gid_t)-1);
805806
assert(extra_group_size < 0);
806807
assert(preexec_fn == Py_None);
807808

809+
/* Drop the GIL so that other threads can continue execution while this
810+
* thread in the parent remains blocked per vfork-semantics on the
811+
* child's exec syscall outcome. Exec requires filesystem access which
812+
* can take an arbitrarily long time. This addresses GH-104372.
813+
*
814+
* The vfork'ed child still runs in our address space. Per POSIX it
815+
* must be limited to nothing but exec, but the Linux implementation
816+
* is a little more usable. See the child_exec() comment.
817+
*/
818+
vfork_tstate_save = PyEval_SaveThread();
808819
pid = vfork();
809820
if (pid == (pid_t)-1) {
821+
PyEval_RestoreThread(vfork_tstate_save);
822+
vfork_tstate_save = NULL;
810823
/* If vfork() fails, fall back to using fork(). When it isn't
811824
* allowed in a process by the kernel, vfork can return -1
812825
* with errno EINVAL. https://bugs.python.org/issue47151. */
@@ -819,6 +832,12 @@ do_fork_exec(char *const exec_array[],
819832
}
820833

821834
if (pid != 0) {
835+
// Parent process.
836+
#ifdef VFORK_USABLE
837+
if (vfork_tstate_save != NULL) {
838+
PyEval_RestoreThread(vfork_tstate_save);
839+
}
840+
#endif
822841
return pid;
823842
}
824843

0 commit comments

Comments
 (0)