bpo-16500: Allow registering at-fork handlers (#1715)
* bpo-16500: Allow registering at-fork handlers * Address Serhiy's comments * Add doc for new C API * Add doc for new Python-facing function * Add NEWS entry + doc nit
This commit is contained in:
parent
f931fd1c2a
commit
346cbd351e
|
@ -26,6 +26,42 @@ Operating System Utilities
|
||||||
one of the strings ``'<stdin>'`` or ``'???'``.
|
one of the strings ``'<stdin>'`` or ``'???'``.
|
||||||
|
|
||||||
|
|
||||||
|
.. c:function:: void PyOS_BeforeFork()
|
||||||
|
|
||||||
|
Function to prepare some internal state before a process fork. This
|
||||||
|
should be called before calling :c:func:`fork` or any similar function
|
||||||
|
that clones the current process.
|
||||||
|
Only available on systems where :c:func:`fork` is defined.
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
|
||||||
|
.. c:function:: void PyOS_AfterFork_Parent()
|
||||||
|
|
||||||
|
Function to update some internal state after a process fork. This
|
||||||
|
should be called from the parent process after calling :c:func:`fork`
|
||||||
|
or any similar function that clones the current process, regardless
|
||||||
|
of whether process cloning was successful.
|
||||||
|
Only available on systems where :c:func:`fork` is defined.
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
|
||||||
|
.. c:function:: void PyOS_AfterFork_Child()
|
||||||
|
|
||||||
|
Function to update some internal state after a process fork. This
|
||||||
|
should be called from the child process after calling :c:func:`fork`
|
||||||
|
or any similar function that clones the current process.
|
||||||
|
Only available on systems where :c:func:`fork` is defined.
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
:func:`os.register_at_fork` allows registering custom Python functions
|
||||||
|
to be called by :c:func:`PyOS_BeforeFork()`,
|
||||||
|
:c:func:`PyOS_AfterFork_Parent` and :c:func:`PyOS_AfterFork_Child`.
|
||||||
|
|
||||||
|
|
||||||
.. c:function:: void PyOS_AfterFork()
|
.. c:function:: void PyOS_AfterFork()
|
||||||
|
|
||||||
Function to update some internal state after a process fork; this should be
|
Function to update some internal state after a process fork; this should be
|
||||||
|
@ -33,6 +69,9 @@ Operating System Utilities
|
||||||
If a new executable is loaded into the new process, this function does not need
|
If a new executable is loaded into the new process, this function does not need
|
||||||
to be called.
|
to be called.
|
||||||
|
|
||||||
|
.. deprecated:: 3.7
|
||||||
|
This function is superseded by :c:func:`PyOS_AfterFork_Child()`.
|
||||||
|
|
||||||
|
|
||||||
.. c:function:: int PyOS_CheckStack()
|
.. c:function:: int PyOS_CheckStack()
|
||||||
|
|
||||||
|
|
|
@ -3280,6 +3280,31 @@ written in Python, such as a mail server's external command delivery program.
|
||||||
subprocesses.
|
subprocesses.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: register_at_fork(func, when)
|
||||||
|
|
||||||
|
Register *func* as a function to be executed when a new child process
|
||||||
|
is forked. *when* is a string specifying at which point the function is
|
||||||
|
called and can take the following values:
|
||||||
|
|
||||||
|
* *"before"* means the function is called before forking a child process;
|
||||||
|
* *"parent"* means the function is called from the parent process after
|
||||||
|
forking a child process;
|
||||||
|
* *"child"* means the function is called from the child process.
|
||||||
|
|
||||||
|
Functions registered for execution before forking are called in
|
||||||
|
reverse registration order. Functions registered for execution
|
||||||
|
after forking (either in the parent or in the child) are called
|
||||||
|
in registration order.
|
||||||
|
|
||||||
|
Note that :c:func:`fork` calls made by third-party C code may not
|
||||||
|
call those functions, unless it explicitly calls :c:func:`PyOS_BeforeFork`,
|
||||||
|
:c:func:`PyOS_AfterFork_Parent` and :c:func:`PyOS_AfterFork_Child`.
|
||||||
|
|
||||||
|
Availability: Unix.
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
|
||||||
.. function:: spawnl(mode, path, ...)
|
.. function:: spawnl(mode, path, ...)
|
||||||
spawnle(mode, path, ..., env)
|
spawnle(mode, path, ..., env)
|
||||||
spawnlp(mode, file, ...)
|
spawnlp(mode, file, ...)
|
||||||
|
|
|
@ -7,10 +7,17 @@ extern "C" {
|
||||||
|
|
||||||
PyAPI_FUNC(int) PyOS_InterruptOccurred(void);
|
PyAPI_FUNC(int) PyOS_InterruptOccurred(void);
|
||||||
PyAPI_FUNC(void) PyOS_InitInterrupts(void);
|
PyAPI_FUNC(void) PyOS_InitInterrupts(void);
|
||||||
PyAPI_FUNC(void) PyOS_AfterFork(void);
|
#ifdef HAVE_FORK
|
||||||
|
PyAPI_FUNC(void) PyOS_BeforeFork(void);
|
||||||
|
PyAPI_FUNC(void) PyOS_AfterFork_Parent(void);
|
||||||
|
PyAPI_FUNC(void) PyOS_AfterFork_Child(void);
|
||||||
|
#endif
|
||||||
|
/* Deprecated, please use PyOS_AfterFork_Child() instead */
|
||||||
|
PyAPI_FUNC(void) PyOS_AfterFork(void) Py_DEPRECATED(3.7);
|
||||||
|
|
||||||
#ifndef Py_LIMITED_API
|
#ifndef Py_LIMITED_API
|
||||||
PyAPI_FUNC(int) _PyOS_IsMainThread(void);
|
PyAPI_FUNC(int) _PyOS_IsMainThread(void);
|
||||||
|
PyAPI_FUNC(void) _PySignal_AfterFork(void);
|
||||||
|
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
/* windows.h is not included by Python.h so use void* instead of HANDLE */
|
/* windows.h is not included by Python.h so use void* instead of HANDLE */
|
||||||
|
|
|
@ -74,6 +74,11 @@ typedef struct _is {
|
||||||
PyObject *import_func;
|
PyObject *import_func;
|
||||||
/* Initialized to PyEval_EvalFrameDefault(). */
|
/* Initialized to PyEval_EvalFrameDefault(). */
|
||||||
_PyFrameEvalFunction eval_frame;
|
_PyFrameEvalFunction eval_frame;
|
||||||
|
#ifdef HAVE_FORK
|
||||||
|
PyObject *before_forkers;
|
||||||
|
PyObject *after_forkers_parent;
|
||||||
|
PyObject *after_forkers_child;
|
||||||
|
#endif
|
||||||
} PyInterpreterState;
|
} PyInterpreterState;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -210,11 +210,6 @@ def _serve_one(s, listener, alive_r, handlers):
|
||||||
# send pid to client processes
|
# send pid to client processes
|
||||||
write_unsigned(child_w, os.getpid())
|
write_unsigned(child_w, os.getpid())
|
||||||
|
|
||||||
# reseed random number generator
|
|
||||||
if 'random' in sys.modules:
|
|
||||||
import random
|
|
||||||
random.seed()
|
|
||||||
|
|
||||||
# run process object received over pipe
|
# run process object received over pipe
|
||||||
code = spawn._main(child_r)
|
code = spawn._main(child_r)
|
||||||
|
|
||||||
|
|
|
@ -68,9 +68,6 @@ class Popen(object):
|
||||||
if self.pid == 0:
|
if self.pid == 0:
|
||||||
try:
|
try:
|
||||||
os.close(parent_r)
|
os.close(parent_r)
|
||||||
if 'random' in sys.modules:
|
|
||||||
import random
|
|
||||||
random.seed()
|
|
||||||
code = process_obj._bootstrap()
|
code = process_obj._bootstrap()
|
||||||
finally:
|
finally:
|
||||||
os._exit(code)
|
os._exit(code)
|
||||||
|
|
|
@ -46,6 +46,7 @@ from _collections_abc import Set as _Set, Sequence as _Sequence
|
||||||
from hashlib import sha512 as _sha512
|
from hashlib import sha512 as _sha512
|
||||||
import itertools as _itertools
|
import itertools as _itertools
|
||||||
import bisect as _bisect
|
import bisect as _bisect
|
||||||
|
import os as _os
|
||||||
|
|
||||||
__all__ = ["Random","seed","random","uniform","randint","choice","sample",
|
__all__ = ["Random","seed","random","uniform","randint","choice","sample",
|
||||||
"randrange","shuffle","normalvariate","lognormvariate",
|
"randrange","shuffle","normalvariate","lognormvariate",
|
||||||
|
@ -763,5 +764,9 @@ getstate = _inst.getstate
|
||||||
setstate = _inst.setstate
|
setstate = _inst.setstate
|
||||||
getrandbits = _inst.getrandbits
|
getrandbits = _inst.getrandbits
|
||||||
|
|
||||||
|
if hasattr(_os, "fork"):
|
||||||
|
_os.register_at_fork(_inst.seed, when='child')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
_test()
|
_test()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"Test posix functions"
|
"Test posix functions"
|
||||||
|
|
||||||
from test import support
|
from test import support
|
||||||
|
from test.support.script_helper import assert_python_ok
|
||||||
android_not_root = support.android_not_root
|
android_not_root = support.android_not_root
|
||||||
|
|
||||||
# Skip these tests if there is no posix module.
|
# Skip these tests if there is no posix module.
|
||||||
|
@ -187,6 +188,45 @@ class PosixTester(unittest.TestCase):
|
||||||
res = posix.waitid(posix.P_PID, pid, posix.WEXITED)
|
res = posix.waitid(posix.P_PID, pid, posix.WEXITED)
|
||||||
self.assertEqual(pid, res.si_pid)
|
self.assertEqual(pid, res.si_pid)
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
|
||||||
|
def test_register_after_fork(self):
|
||||||
|
code = """if 1:
|
||||||
|
import os
|
||||||
|
|
||||||
|
r, w = os.pipe()
|
||||||
|
fin_r, fin_w = os.pipe()
|
||||||
|
|
||||||
|
os.register_at_fork(lambda: os.write(w, b'A'), when='before')
|
||||||
|
os.register_at_fork(lambda: os.write(w, b'B'), when='before')
|
||||||
|
os.register_at_fork(lambda: os.write(w, b'C'), when='parent')
|
||||||
|
os.register_at_fork(lambda: os.write(w, b'D'), when='parent')
|
||||||
|
os.register_at_fork(lambda: os.write(w, b'E'), when='child')
|
||||||
|
os.register_at_fork(lambda: os.write(w, b'F'), when='child')
|
||||||
|
|
||||||
|
pid = os.fork()
|
||||||
|
if pid == 0:
|
||||||
|
# At this point, after-forkers have already been executed
|
||||||
|
os.close(w)
|
||||||
|
# Wait for parent to tell us to exit
|
||||||
|
os.read(fin_r, 1)
|
||||||
|
os._exit(0)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.close(w)
|
||||||
|
with open(r, "rb") as f:
|
||||||
|
data = f.read()
|
||||||
|
assert len(data) == 6, data
|
||||||
|
# Check before-fork callbacks
|
||||||
|
assert data[:2] == b'BA', data
|
||||||
|
# Check after-fork callbacks
|
||||||
|
assert sorted(data[2:]) == list(b'CDEF'), data
|
||||||
|
assert data.index(b'C') < data.index(b'D'), data
|
||||||
|
assert data.index(b'E') < data.index(b'F'), data
|
||||||
|
finally:
|
||||||
|
os.write(fin_w, b'!')
|
||||||
|
"""
|
||||||
|
assert_python_ok('-c', code)
|
||||||
|
|
||||||
@unittest.skipUnless(hasattr(posix, 'lockf'), "test needs posix.lockf()")
|
@unittest.skipUnless(hasattr(posix, 'lockf'), "test needs posix.lockf()")
|
||||||
def test_lockf(self):
|
def test_lockf(self):
|
||||||
fd = os.open(support.TESTFN, os.O_WRONLY | os.O_CREAT)
|
fd = os.open(support.TESTFN, os.O_WRONLY | os.O_CREAT)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
import unittest.mock
|
import unittest.mock
|
||||||
import random
|
import random
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
import pickle
|
import pickle
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -902,6 +903,24 @@ class TestModule(unittest.TestCase):
|
||||||
random.Random.__init__(self)
|
random.Random.__init__(self)
|
||||||
Subclass(newarg=1)
|
Subclass(newarg=1)
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(os, "fork"), "fork() required")
|
||||||
|
def test_after_fork(self):
|
||||||
|
# Test the global Random instance gets reseeded in child
|
||||||
|
r, w = os.pipe()
|
||||||
|
if os.fork() == 0:
|
||||||
|
try:
|
||||||
|
val = random.getrandbits(128)
|
||||||
|
with open(w, "w") as f:
|
||||||
|
f.write(str(val))
|
||||||
|
finally:
|
||||||
|
os._exit(0)
|
||||||
|
else:
|
||||||
|
os.close(w)
|
||||||
|
val = random.getrandbits(128)
|
||||||
|
with open(r, "r") as f:
|
||||||
|
child_val = eval(f.read())
|
||||||
|
self.assertNotEqual(val, child_val)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -341,6 +341,8 @@ Extension Modules
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- bpo-16500: Allow registering at-fork handlers.
|
||||||
|
|
||||||
- bpo-30470: Deprecate invalid ctypes call protection on Windows. Patch by
|
- bpo-30470: Deprecate invalid ctypes call protection on Windows. Patch by
|
||||||
Mariatta Wijaya.
|
Mariatta Wijaya.
|
||||||
|
|
||||||
|
|
|
@ -559,9 +559,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
|
||||||
int need_to_reenable_gc = 0;
|
int need_to_reenable_gc = 0;
|
||||||
char *const *exec_array, *const *argv = NULL, *const *envp = NULL;
|
char *const *exec_array, *const *argv = NULL, *const *envp = NULL;
|
||||||
Py_ssize_t arg_num;
|
Py_ssize_t arg_num;
|
||||||
#ifdef WITH_THREAD
|
int need_after_fork = 0;
|
||||||
int import_lock_held = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "OOpO!OOiiiiiiiiiiO:fork_exec",
|
args, "OOpO!OOiiiiiiiiiiO:fork_exec",
|
||||||
|
@ -657,10 +655,8 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
|
||||||
preexec_fn_args_tuple = PyTuple_New(0);
|
preexec_fn_args_tuple = PyTuple_New(0);
|
||||||
if (!preexec_fn_args_tuple)
|
if (!preexec_fn_args_tuple)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
#ifdef WITH_THREAD
|
PyOS_BeforeFork();
|
||||||
_PyImport_AcquireLock();
|
need_after_fork = 1;
|
||||||
import_lock_held = 1;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cwd_obj != Py_None) {
|
if (cwd_obj != Py_None) {
|
||||||
|
@ -686,7 +682,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
|
||||||
* This call may not be async-signal-safe but neither is calling
|
* This call may not be async-signal-safe but neither is calling
|
||||||
* back into Python. The user asked us to use hope as a strategy
|
* back into Python. The user asked us to use hope as a strategy
|
||||||
* to avoid deadlock... */
|
* to avoid deadlock... */
|
||||||
PyOS_AfterFork();
|
PyOS_AfterFork_Child();
|
||||||
}
|
}
|
||||||
|
|
||||||
child_exec(exec_array, argv, envp, cwd,
|
child_exec(exec_array, argv, envp, cwd,
|
||||||
|
@ -703,17 +699,10 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
|
||||||
/* Capture the errno exception before errno can be clobbered. */
|
/* Capture the errno exception before errno can be clobbered. */
|
||||||
PyErr_SetFromErrno(PyExc_OSError);
|
PyErr_SetFromErrno(PyExc_OSError);
|
||||||
}
|
}
|
||||||
#ifdef WITH_THREAD
|
|
||||||
if (preexec_fn != Py_None
|
|
||||||
&& _PyImport_ReleaseLock() < 0 && !PyErr_Occurred()) {
|
|
||||||
PyErr_SetString(PyExc_RuntimeError,
|
|
||||||
"not holding the import lock");
|
|
||||||
pid = -1;
|
|
||||||
}
|
|
||||||
import_lock_held = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Parent process */
|
/* Parent process */
|
||||||
|
if (need_after_fork)
|
||||||
|
PyOS_AfterFork_Parent();
|
||||||
if (envp)
|
if (envp)
|
||||||
_Py_FreeCharPArray(envp);
|
_Py_FreeCharPArray(envp);
|
||||||
if (argv)
|
if (argv)
|
||||||
|
@ -733,10 +722,8 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
|
||||||
return PyLong_FromPid(pid);
|
return PyLong_FromPid(pid);
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
#ifdef WITH_THREAD
|
if (need_after_fork)
|
||||||
if (import_lock_held)
|
PyOS_AfterFork_Parent();
|
||||||
_PyImport_ReleaseLock();
|
|
||||||
#endif
|
|
||||||
if (envp)
|
if (envp)
|
||||||
_Py_FreeCharPArray(envp);
|
_Py_FreeCharPArray(envp);
|
||||||
if (argv)
|
if (argv)
|
||||||
|
|
|
@ -1825,6 +1825,50 @@ exit:
|
||||||
|
|
||||||
#endif /* (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV)) */
|
#endif /* (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV)) */
|
||||||
|
|
||||||
|
#if defined(HAVE_FORK)
|
||||||
|
|
||||||
|
PyDoc_STRVAR(os_register_at_fork__doc__,
|
||||||
|
"register_at_fork($module, func, /, when)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Register a callable object to be called when forking.\n"
|
||||||
|
"\n"
|
||||||
|
" func\n"
|
||||||
|
" Function or callable\n"
|
||||||
|
" when\n"
|
||||||
|
" \'before\', \'child\' or \'parent\'\n"
|
||||||
|
"\n"
|
||||||
|
"\'before\' callbacks are called in reverse order before forking.\n"
|
||||||
|
"\'child\' callbacks are called in order after forking, in the child process.\n"
|
||||||
|
"\'parent\' callbacks are called in order after forking, in the parent process.");
|
||||||
|
|
||||||
|
#define OS_REGISTER_AT_FORK_METHODDEF \
|
||||||
|
{"register_at_fork", (PyCFunction)os_register_at_fork, METH_FASTCALL, os_register_at_fork__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
os_register_at_fork_impl(PyObject *module, PyObject *func, const char *when);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
os_register_at_fork(PyObject *module, PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
static const char * const _keywords[] = {"", "when", NULL};
|
||||||
|
static _PyArg_Parser _parser = {"Os:register_at_fork", _keywords, 0};
|
||||||
|
PyObject *func;
|
||||||
|
const char *when;
|
||||||
|
|
||||||
|
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
|
||||||
|
&func, &when)) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
return_value = os_register_at_fork_impl(module, func, when);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* defined(HAVE_FORK) */
|
||||||
|
|
||||||
#if defined(HAVE_FORK1)
|
#if defined(HAVE_FORK1)
|
||||||
|
|
||||||
PyDoc_STRVAR(os_fork1__doc__,
|
PyDoc_STRVAR(os_fork1__doc__,
|
||||||
|
@ -6122,6 +6166,10 @@ exit:
|
||||||
#define OS_SPAWNVE_METHODDEF
|
#define OS_SPAWNVE_METHODDEF
|
||||||
#endif /* !defined(OS_SPAWNVE_METHODDEF) */
|
#endif /* !defined(OS_SPAWNVE_METHODDEF) */
|
||||||
|
|
||||||
|
#ifndef OS_REGISTER_AT_FORK_METHODDEF
|
||||||
|
#define OS_REGISTER_AT_FORK_METHODDEF
|
||||||
|
#endif /* !defined(OS_REGISTER_AT_FORK_METHODDEF) */
|
||||||
|
|
||||||
#ifndef OS_FORK1_METHODDEF
|
#ifndef OS_FORK1_METHODDEF
|
||||||
#define OS_FORK1_METHODDEF
|
#define OS_FORK1_METHODDEF
|
||||||
#endif /* !defined(OS_FORK1_METHODDEF) */
|
#endif /* !defined(OS_FORK1_METHODDEF) */
|
||||||
|
@ -6493,4 +6541,4 @@ exit:
|
||||||
#ifndef OS_GETRANDOM_METHODDEF
|
#ifndef OS_GETRANDOM_METHODDEF
|
||||||
#define OS_GETRANDOM_METHODDEF
|
#define OS_GETRANDOM_METHODDEF
|
||||||
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
|
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
|
||||||
/*[clinic end generated code: output=5529857101c08b49 input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=699e11c5579a104e input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#define PY_SSIZE_T_CLEAN
|
#define PY_SSIZE_T_CLEAN
|
||||||
|
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
|
#include "pythread.h"
|
||||||
#include "structmember.h"
|
#include "structmember.h"
|
||||||
#ifndef MS_WINDOWS
|
#ifndef MS_WINDOWS
|
||||||
#include "posixmodule.h"
|
#include "posixmodule.h"
|
||||||
|
@ -394,6 +395,95 @@ static int win32_can_symlink = 0;
|
||||||
#define MODNAME "posix"
|
#define MODNAME "posix"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef HAVE_FORK
|
||||||
|
static void
|
||||||
|
run_at_forkers(PyObject *lst, int reverse)
|
||||||
|
{
|
||||||
|
Py_ssize_t i;
|
||||||
|
PyObject *cpy;
|
||||||
|
|
||||||
|
if (lst != NULL) {
|
||||||
|
assert(PyList_CheckExact(lst));
|
||||||
|
|
||||||
|
/* Use a list copy in case register_at_fork() is called from
|
||||||
|
* one of the callbacks.
|
||||||
|
*/
|
||||||
|
cpy = PyList_GetSlice(lst, 0, PyList_GET_SIZE(lst));
|
||||||
|
if (cpy == NULL)
|
||||||
|
PyErr_WriteUnraisable(lst);
|
||||||
|
else {
|
||||||
|
if (reverse)
|
||||||
|
PyList_Reverse(cpy);
|
||||||
|
for (i = 0; i < PyList_GET_SIZE(cpy); i++) {
|
||||||
|
PyObject *func, *res;
|
||||||
|
func = PyList_GET_ITEM(cpy, i);
|
||||||
|
res = PyObject_CallObject(func, NULL);
|
||||||
|
if (res == NULL)
|
||||||
|
PyErr_WriteUnraisable(func);
|
||||||
|
else
|
||||||
|
Py_DECREF(res);
|
||||||
|
}
|
||||||
|
Py_DECREF(cpy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PyOS_BeforeFork(void)
|
||||||
|
{
|
||||||
|
run_at_forkers(PyThreadState_Get()->interp->before_forkers, 1);
|
||||||
|
|
||||||
|
_PyImport_AcquireLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PyOS_AfterFork_Parent(void)
|
||||||
|
{
|
||||||
|
if (_PyImport_ReleaseLock() <= 0)
|
||||||
|
Py_FatalError("failed releasing import lock after fork");
|
||||||
|
|
||||||
|
run_at_forkers(PyThreadState_Get()->interp->after_forkers_parent, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PyOS_AfterFork_Child(void)
|
||||||
|
{
|
||||||
|
#ifdef WITH_THREAD
|
||||||
|
/* PyThread_ReInitTLS() must be called early, to make sure that the TLS API
|
||||||
|
* can be called safely. */
|
||||||
|
PyThread_ReInitTLS();
|
||||||
|
_PyGILState_Reinit();
|
||||||
|
PyEval_ReInitThreads();
|
||||||
|
_PyImport_ReInitLock();
|
||||||
|
#endif
|
||||||
|
_PySignal_AfterFork();
|
||||||
|
|
||||||
|
run_at_forkers(PyThreadState_Get()->interp->after_forkers_child, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
register_at_forker(PyObject **lst, PyObject *func)
|
||||||
|
{
|
||||||
|
if (*lst == NULL) {
|
||||||
|
*lst = PyList_New(0);
|
||||||
|
if (*lst == NULL)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return PyList_Append(*lst, func);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Legacy wrapper */
|
||||||
|
void
|
||||||
|
PyOS_AfterFork(void)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_FORK
|
||||||
|
PyOS_AfterFork_Child();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
/* defined in fileutils.c */
|
/* defined in fileutils.c */
|
||||||
PyAPI_FUNC(void) _Py_time_t_to_FILE_TIME(time_t, int, FILETIME *);
|
PyAPI_FUNC(void) _Py_time_t_to_FILE_TIME(time_t, int, FILETIME *);
|
||||||
|
@ -5218,6 +5308,57 @@ os_spawnve_impl(PyObject *module, int mode, path_t *path, PyObject *argv,
|
||||||
#endif /* HAVE_SPAWNV */
|
#endif /* HAVE_SPAWNV */
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef HAVE_FORK
|
||||||
|
/*[clinic input]
|
||||||
|
os.register_at_fork
|
||||||
|
|
||||||
|
func: object
|
||||||
|
Function or callable
|
||||||
|
/
|
||||||
|
when: str
|
||||||
|
'before', 'child' or 'parent'
|
||||||
|
|
||||||
|
Register a callable object to be called when forking.
|
||||||
|
|
||||||
|
'before' callbacks are called in reverse order before forking.
|
||||||
|
'child' callbacks are called in order after forking, in the child process.
|
||||||
|
'parent' callbacks are called in order after forking, in the parent process.
|
||||||
|
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
os_register_at_fork_impl(PyObject *module, PyObject *func, const char *when)
|
||||||
|
/*[clinic end generated code: output=8943be81a644750c input=5fc05efa4d42eb84]*/
|
||||||
|
{
|
||||||
|
PyInterpreterState *interp;
|
||||||
|
PyObject **lst;
|
||||||
|
|
||||||
|
if (!PyCallable_Check(func)) {
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"expected callable object, got %R", Py_TYPE(func));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
interp = PyThreadState_Get()->interp;
|
||||||
|
|
||||||
|
if (!strcmp(when, "before"))
|
||||||
|
lst = &interp->before_forkers;
|
||||||
|
else if (!strcmp(when, "child"))
|
||||||
|
lst = &interp->after_forkers_child;
|
||||||
|
else if (!strcmp(when, "parent"))
|
||||||
|
lst = &interp->after_forkers_parent;
|
||||||
|
else {
|
||||||
|
PyErr_Format(PyExc_ValueError, "unexpected value for `when`: '%s'",
|
||||||
|
when);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (register_at_forker(lst, func))
|
||||||
|
return NULL;
|
||||||
|
else
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
#endif /* HAVE_FORK */
|
||||||
|
|
||||||
|
|
||||||
#ifdef HAVE_FORK1
|
#ifdef HAVE_FORK1
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
os.fork1
|
os.fork1
|
||||||
|
@ -5232,24 +5373,18 @@ os_fork1_impl(PyObject *module)
|
||||||
/*[clinic end generated code: output=0de8e67ce2a310bc input=12db02167893926e]*/
|
/*[clinic end generated code: output=0de8e67ce2a310bc input=12db02167893926e]*/
|
||||||
{
|
{
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
int result = 0;
|
|
||||||
_PyImport_AcquireLock();
|
PyOS_BeforeFork();
|
||||||
pid = fork1();
|
pid = fork1();
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
/* child: this clobbers and resets the import lock. */
|
/* child: this clobbers and resets the import lock. */
|
||||||
PyOS_AfterFork();
|
PyOS_AfterFork_Child();
|
||||||
} else {
|
} else {
|
||||||
/* parent: release the import lock. */
|
/* parent: release the import lock. */
|
||||||
result = _PyImport_ReleaseLock();
|
PyOS_AfterFork_Parent();
|
||||||
}
|
}
|
||||||
if (pid == -1)
|
if (pid == -1)
|
||||||
return posix_error();
|
return posix_error();
|
||||||
if (result < 0) {
|
|
||||||
/* Don't clobber the OSError if the fork failed. */
|
|
||||||
PyErr_SetString(PyExc_RuntimeError,
|
|
||||||
"not holding the import lock");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return PyLong_FromPid(pid);
|
return PyLong_FromPid(pid);
|
||||||
}
|
}
|
||||||
#endif /* HAVE_FORK1 */
|
#endif /* HAVE_FORK1 */
|
||||||
|
@ -5269,24 +5404,18 @@ os_fork_impl(PyObject *module)
|
||||||
/*[clinic end generated code: output=3626c81f98985d49 input=13c956413110eeaa]*/
|
/*[clinic end generated code: output=3626c81f98985d49 input=13c956413110eeaa]*/
|
||||||
{
|
{
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
int result = 0;
|
|
||||||
_PyImport_AcquireLock();
|
PyOS_BeforeFork();
|
||||||
pid = fork();
|
pid = fork();
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
/* child: this clobbers and resets the import lock. */
|
/* child: this clobbers and resets the import lock. */
|
||||||
PyOS_AfterFork();
|
PyOS_AfterFork_Child();
|
||||||
} else {
|
} else {
|
||||||
/* parent: release the import lock. */
|
/* parent: release the import lock. */
|
||||||
result = _PyImport_ReleaseLock();
|
PyOS_AfterFork_Parent();
|
||||||
}
|
}
|
||||||
if (pid == -1)
|
if (pid == -1)
|
||||||
return posix_error();
|
return posix_error();
|
||||||
if (result < 0) {
|
|
||||||
/* Don't clobber the OSError if the fork failed. */
|
|
||||||
PyErr_SetString(PyExc_RuntimeError,
|
|
||||||
"not holding the import lock");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return PyLong_FromPid(pid);
|
return PyLong_FromPid(pid);
|
||||||
}
|
}
|
||||||
#endif /* HAVE_FORK */
|
#endif /* HAVE_FORK */
|
||||||
|
@ -5868,26 +5997,20 @@ static PyObject *
|
||||||
os_forkpty_impl(PyObject *module)
|
os_forkpty_impl(PyObject *module)
|
||||||
/*[clinic end generated code: output=60d0a5c7512e4087 input=f1f7f4bae3966010]*/
|
/*[clinic end generated code: output=60d0a5c7512e4087 input=f1f7f4bae3966010]*/
|
||||||
{
|
{
|
||||||
int master_fd = -1, result = 0;
|
int master_fd = -1;
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
|
|
||||||
_PyImport_AcquireLock();
|
PyOS_BeforeFork();
|
||||||
pid = forkpty(&master_fd, NULL, NULL, NULL);
|
pid = forkpty(&master_fd, NULL, NULL, NULL);
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
/* child: this clobbers and resets the import lock. */
|
/* child: this clobbers and resets the import lock. */
|
||||||
PyOS_AfterFork();
|
PyOS_AfterFork_Child();
|
||||||
} else {
|
} else {
|
||||||
/* parent: release the import lock. */
|
/* parent: release the import lock. */
|
||||||
result = _PyImport_ReleaseLock();
|
PyOS_AfterFork_Parent();
|
||||||
}
|
}
|
||||||
if (pid == -1)
|
if (pid == -1)
|
||||||
return posix_error();
|
return posix_error();
|
||||||
if (result < 0) {
|
|
||||||
/* Don't clobber the OSError if the fork failed. */
|
|
||||||
PyErr_SetString(PyExc_RuntimeError,
|
|
||||||
"not holding the import lock");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return Py_BuildValue("(Ni)", PyLong_FromPid(pid), master_fd);
|
return Py_BuildValue("(Ni)", PyLong_FromPid(pid), master_fd);
|
||||||
}
|
}
|
||||||
#endif /* HAVE_FORKPTY */
|
#endif /* HAVE_FORKPTY */
|
||||||
|
@ -12265,6 +12388,7 @@ static PyMethodDef posix_methods[] = {
|
||||||
OS_SPAWNVE_METHODDEF
|
OS_SPAWNVE_METHODDEF
|
||||||
OS_FORK1_METHODDEF
|
OS_FORK1_METHODDEF
|
||||||
OS_FORK_METHODDEF
|
OS_FORK_METHODDEF
|
||||||
|
OS_REGISTER_AT_FORK_METHODDEF
|
||||||
OS_SCHED_GET_PRIORITY_MAX_METHODDEF
|
OS_SCHED_GET_PRIORITY_MAX_METHODDEF
|
||||||
OS_SCHED_GET_PRIORITY_MIN_METHODDEF
|
OS_SCHED_GET_PRIORITY_MIN_METHODDEF
|
||||||
OS_SCHED_GETPARAM_METHODDEF
|
OS_SCHED_GETPARAM_METHODDEF
|
||||||
|
|
|
@ -1618,21 +1618,15 @@ _clear_pending_signals(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PyOS_AfterFork(void)
|
_PySignal_AfterFork(void)
|
||||||
{
|
{
|
||||||
/* Clear the signal flags after forking so that they aren't handled
|
/* Clear the signal flags after forking so that they aren't handled
|
||||||
* in both processes if they came in just before the fork() but before
|
* in both processes if they came in just before the fork() but before
|
||||||
* the interpreter had an opportunity to call the handlers. issue9535. */
|
* the interpreter had an opportunity to call the handlers. issue9535. */
|
||||||
_clear_pending_signals();
|
_clear_pending_signals();
|
||||||
#ifdef WITH_THREAD
|
#ifdef WITH_THREAD
|
||||||
/* PyThread_ReInitTLS() must be called early, to make sure that the TLS API
|
|
||||||
* can be called safely. */
|
|
||||||
PyThread_ReInitTLS();
|
|
||||||
_PyGILState_Reinit();
|
|
||||||
PyEval_ReInitThreads();
|
|
||||||
main_thread = PyThread_get_thread_ident();
|
main_thread = PyThread_get_thread_ident();
|
||||||
main_pid = getpid();
|
main_pid = getpid();
|
||||||
_PyImport_ReInitLock();
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -117,6 +117,11 @@ PyInterpreterState_New(void)
|
||||||
#else
|
#else
|
||||||
interp->dlopenflags = RTLD_LAZY;
|
interp->dlopenflags = RTLD_LAZY;
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_FORK
|
||||||
|
interp->before_forkers = NULL;
|
||||||
|
interp->after_forkers_parent = NULL;
|
||||||
|
interp->after_forkers_child = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
HEAD_LOCK();
|
HEAD_LOCK();
|
||||||
|
@ -159,6 +164,11 @@ PyInterpreterState_Clear(PyInterpreterState *interp)
|
||||||
Py_CLEAR(interp->builtins_copy);
|
Py_CLEAR(interp->builtins_copy);
|
||||||
Py_CLEAR(interp->importlib);
|
Py_CLEAR(interp->importlib);
|
||||||
Py_CLEAR(interp->import_func);
|
Py_CLEAR(interp->import_func);
|
||||||
|
#ifdef HAVE_FORK
|
||||||
|
Py_CLEAR(interp->before_forkers);
|
||||||
|
Py_CLEAR(interp->after_forkers_parent);
|
||||||
|
Py_CLEAR(interp->after_forkers_child);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue