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

Skip to content

Commit 163468a

Browse files
authored
bpo-16500: Don't use string constants for os.register_at_fork() behavior (#1834)
Instead use keyword only arguments to os.register_at_fork for each of the scenarios. Updates the documentation for clarity.
1 parent eba68e2 commit 163468a

File tree

8 files changed

+128
-75
lines changed

8 files changed

+128
-75
lines changed

Doc/c-api/sys.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ Operating System Utilities
4949
5050
.. c:function:: void PyOS_AfterFork_Child()
5151
52-
Function to update some internal state after a process fork. This
53-
should be called from the child process after calling :c:func:`fork`
54-
or any similar function that clones the current process.
52+
Function to update internal interpreter state after a process fork.
53+
This must be called from the child process after calling :c:func:`fork`,
54+
or any similar function that clones the current process, if there is
55+
any chance the process will call back into the Python interpreter.
5556
Only available on systems where :c:func:`fork` is defined.
5657
5758
.. versionadded:: 3.7

Doc/library/os.rst

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3280,16 +3280,22 @@ written in Python, such as a mail server's external command delivery program.
32803280
subprocesses.
32813281

32823282

3283-
.. function:: register_at_fork(func, when)
3283+
.. function:: register_at_fork(*, before=None, after_in_parent=None, \
3284+
after_in_child=None)
32843285

3285-
Register *func* as a function to be executed when a new child process
3286-
is forked. *when* is a string specifying at which point the function is
3287-
called and can take the following values:
3286+
Register callables to be executed when a new child process is forked
3287+
using :func:`os.fork` or similar process cloning APIs.
3288+
The parameters are optional and keyword-only.
3289+
Each specifies a different call point.
32883290

3289-
* *"before"* means the function is called before forking a child process;
3290-
* *"parent"* means the function is called from the parent process after
3291-
forking a child process;
3292-
* *"child"* means the function is called from the child process.
3291+
* *before* is a function called before forking a child process.
3292+
* *after_in_parent* is a function called from the parent process
3293+
after forking a child process.
3294+
* *after_in_child* is a function called from the child process.
3295+
3296+
These calls are only made if control is expected to return to the
3297+
Python interpreter. A typical :mod:`subprocess` launch will not
3298+
trigger them as the child is not going to re-enter the interpreter.
32933299

32943300
Functions registered for execution before forking are called in
32953301
reverse registration order. Functions registered for execution
@@ -3300,6 +3306,8 @@ written in Python, such as a mail server's external command delivery program.
33003306
call those functions, unless it explicitly calls :c:func:`PyOS_BeforeFork`,
33013307
:c:func:`PyOS_AfterFork_Parent` and :c:func:`PyOS_AfterFork_Child`.
33023308

3309+
There is no way to unregister a function.
3310+
33033311
Availability: Unix.
33043312

33053313
.. versionadded:: 3.7

Lib/random.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,7 @@ def _test(N=2000):
765765
getrandbits = _inst.getrandbits
766766

767767
if hasattr(_os, "fork"):
768-
_os.register_at_fork(_inst.seed, when='child')
768+
_os.register_at_fork(after_in_child=_inst.seed)
769769

770770

771771
if __name__ == '__main__':

Lib/test/test_posix.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -189,19 +189,41 @@ def test_waitid(self):
189189
self.assertEqual(pid, res.si_pid)
190190

191191
@unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
192-
def test_register_after_fork(self):
192+
def test_register_at_fork(self):
193+
with self.assertRaises(TypeError, msg="Positional args not allowed"):
194+
os.register_at_fork(lambda: None)
195+
with self.assertRaises(TypeError, msg="Args must be callable"):
196+
os.register_at_fork(before=2)
197+
with self.assertRaises(TypeError, msg="Args must be callable"):
198+
os.register_at_fork(after_in_child="three")
199+
with self.assertRaises(TypeError, msg="Args must be callable"):
200+
os.register_at_fork(after_in_parent=b"Five")
201+
with self.assertRaises(TypeError, msg="Args must not be None"):
202+
os.register_at_fork(before=None)
203+
with self.assertRaises(TypeError, msg="Args must not be None"):
204+
os.register_at_fork(after_in_child=None)
205+
with self.assertRaises(TypeError, msg="Args must not be None"):
206+
os.register_at_fork(after_in_parent=None)
207+
with self.assertRaises(TypeError, msg="Invalid arg was allowed"):
208+
# Ensure a combination of valid and invalid is an error.
209+
os.register_at_fork(before=None, after_in_parent=lambda: 3)
210+
with self.assertRaises(TypeError, msg="Invalid arg was allowed"):
211+
# Ensure a combination of valid and invalid is an error.
212+
os.register_at_fork(before=lambda: None, after_in_child='')
213+
# We test actual registrations in their own process so as not to
214+
# pollute this one. There is no way to unregister for cleanup.
193215
code = """if 1:
194216
import os
195217
196218
r, w = os.pipe()
197219
fin_r, fin_w = os.pipe()
198220
199-
os.register_at_fork(lambda: os.write(w, b'A'), when='before')
200-
os.register_at_fork(lambda: os.write(w, b'B'), when='before')
201-
os.register_at_fork(lambda: os.write(w, b'C'), when='parent')
202-
os.register_at_fork(lambda: os.write(w, b'D'), when='parent')
203-
os.register_at_fork(lambda: os.write(w, b'E'), when='child')
204-
os.register_at_fork(lambda: os.write(w, b'F'), when='child')
221+
os.register_at_fork(before=lambda: os.write(w, b'A'))
222+
os.register_at_fork(after_in_parent=lambda: os.write(w, b'C'))
223+
os.register_at_fork(after_in_child=lambda: os.write(w, b'E'))
224+
os.register_at_fork(before=lambda: os.write(w, b'B'),
225+
after_in_parent=lambda: os.write(w, b'D'),
226+
after_in_child=lambda: os.write(w, b'F'))
205227
206228
pid = os.fork()
207229
if pid == 0:

Lib/threading.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,5 +1359,5 @@ def _after_fork():
13591359
assert len(_active) == 1
13601360

13611361

1362-
if hasattr(_os, "fork"):
1363-
_os.register_at_fork(_after_fork, when="child")
1362+
if hasattr(_os, "register_at_fork"):
1363+
_os.register_at_fork(after_in_child=_after_fork)

Modules/_posixsubprocess.c

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -651,14 +651,6 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
651651
goto cleanup;
652652
}
653653

654-
if (preexec_fn != Py_None) {
655-
preexec_fn_args_tuple = PyTuple_New(0);
656-
if (!preexec_fn_args_tuple)
657-
goto cleanup;
658-
PyOS_BeforeFork();
659-
need_after_fork = 1;
660-
}
661-
662654
if (cwd_obj != Py_None) {
663655
if (PyUnicode_FSConverter(cwd_obj, &cwd_obj2) == 0)
664656
goto cleanup;
@@ -668,6 +660,17 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
668660
cwd_obj2 = NULL;
669661
}
670662

663+
/* This must be the last thing done before fork() because we do not
664+
* want to call PyOS_BeforeFork() if there is any chance of another
665+
* error leading to the cleanup: code without calling fork(). */
666+
if (preexec_fn != Py_None) {
667+
preexec_fn_args_tuple = PyTuple_New(0);
668+
if (!preexec_fn_args_tuple)
669+
goto cleanup;
670+
PyOS_BeforeFork();
671+
need_after_fork = 1;
672+
}
673+
671674
pid = fork();
672675
if (pid == 0) {
673676
/* Child process */
@@ -722,8 +725,6 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
722725
return PyLong_FromPid(pid);
723726

724727
cleanup:
725-
if (need_after_fork)
726-
PyOS_AfterFork_Parent();
727728
if (envp)
728729
_Py_FreeCharPArray(envp);
729730
if (argv)

Modules/clinic/posixmodule.c.h

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,40 +1828,44 @@ os_spawnve(PyObject *module, PyObject **args, Py_ssize_t nargs, PyObject *kwname
18281828
#if defined(HAVE_FORK)
18291829

18301830
PyDoc_STRVAR(os_register_at_fork__doc__,
1831-
"register_at_fork($module, func, /, when)\n"
1831+
"register_at_fork($module, /, *, before=None, after_in_child=None,\n"
1832+
" after_in_parent=None)\n"
18321833
"--\n"
18331834
"\n"
1834-
"Register a callable object to be called when forking.\n"
1835+
"Register callables to be called when forking a new process.\n"
18351836
"\n"
1836-
" func\n"
1837-
" Function or callable\n"
1838-
" when\n"
1839-
" \'before\', \'child\' or \'parent\'\n"
1837+
" before\n"
1838+
" A callable to be called in the parent before the fork() syscall.\n"
1839+
" after_in_child\n"
1840+
" A callable to be called in the child after fork().\n"
1841+
" after_in_parent\n"
1842+
" A callable to be called in the parent after fork().\n"
18401843
"\n"
1841-
"\'before\' callbacks are called in reverse order before forking.\n"
1842-
"\'child\' callbacks are called in order after forking, in the child process.\n"
1843-
"\'parent\' callbacks are called in order after forking, in the parent process.");
1844+
"\'before\' callbacks are called in reverse order.\n"
1845+
"\'after_in_child\' and \'after_in_parent\' callbacks are called in order.");
18441846

18451847
#define OS_REGISTER_AT_FORK_METHODDEF \
18461848
{"register_at_fork", (PyCFunction)os_register_at_fork, METH_FASTCALL, os_register_at_fork__doc__},
18471849

18481850
static PyObject *
1849-
os_register_at_fork_impl(PyObject *module, PyObject *func, const char *when);
1851+
os_register_at_fork_impl(PyObject *module, PyObject *before,
1852+
PyObject *after_in_child, PyObject *after_in_parent);
18501853

18511854
static PyObject *
18521855
os_register_at_fork(PyObject *module, PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
18531856
{
18541857
PyObject *return_value = NULL;
1855-
static const char * const _keywords[] = {"", "when", NULL};
1856-
static _PyArg_Parser _parser = {"Os:register_at_fork", _keywords, 0};
1857-
PyObject *func;
1858-
const char *when;
1858+
static const char * const _keywords[] = {"before", "after_in_child", "after_in_parent", NULL};
1859+
static _PyArg_Parser _parser = {"|$OOO:register_at_fork", _keywords, 0};
1860+
PyObject *before = NULL;
1861+
PyObject *after_in_child = NULL;
1862+
PyObject *after_in_parent = NULL;
18591863

18601864
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
1861-
&func, &when)) {
1865+
&before, &after_in_child, &after_in_parent)) {
18621866
goto exit;
18631867
}
1864-
return_value = os_register_at_fork_impl(module, func, when);
1868+
return_value = os_register_at_fork_impl(module, before, after_in_child, after_in_parent);
18651869

18661870
exit:
18671871
return return_value;
@@ -6541,4 +6545,4 @@ os_getrandom(PyObject *module, PyObject **args, Py_ssize_t nargs, PyObject *kwna
65416545
#ifndef OS_GETRANDOM_METHODDEF
65426546
#define OS_GETRANDOM_METHODDEF
65436547
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
6544-
/*[clinic end generated code: output=699e11c5579a104e input=a9049054013a1b77]*/
6548+
/*[clinic end generated code: output=dce741f527ddbfa4 input=a9049054013a1b77]*/

Modules/posixmodule.c

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,8 @@ PyOS_AfterFork_Child(void)
465465
static int
466466
register_at_forker(PyObject **lst, PyObject *func)
467467
{
468+
if (func == NULL) /* nothing to register? do nothing. */
469+
return 0;
468470
if (*lst == NULL) {
469471
*lst = PyList_New(0);
470472
if (*lst == NULL)
@@ -5309,52 +5311,67 @@ os_spawnve_impl(PyObject *module, int mode, path_t *path, PyObject *argv,
53095311

53105312

53115313
#ifdef HAVE_FORK
5314+
5315+
/* Helper function to validate arguments.
5316+
Returns 0 on success. non-zero on failure with a TypeError raised.
5317+
If obj is non-NULL it must be callable. */
5318+
static int
5319+
check_null_or_callable(PyObject *obj, const char* obj_name)
5320+
{
5321+
if (obj && !PyCallable_Check(obj)) {
5322+
PyErr_Format(PyExc_TypeError, "'%s' must be callable, not %s",
5323+
obj_name, Py_TYPE(obj)->tp_name);
5324+
return -1;
5325+
}
5326+
return 0;
5327+
}
5328+
53125329
/*[clinic input]
53135330
os.register_at_fork
53145331
5315-
func: object
5316-
Function or callable
5317-
/
5318-
when: str
5319-
'before', 'child' or 'parent'
5332+
*
5333+
before: object=NULL
5334+
A callable to be called in the parent before the fork() syscall.
5335+
after_in_child: object=NULL
5336+
A callable to be called in the child after fork().
5337+
after_in_parent: object=NULL
5338+
A callable to be called in the parent after fork().
53205339
5321-
Register a callable object to be called when forking.
5340+
Register callables to be called when forking a new process.
53225341
5323-
'before' callbacks are called in reverse order before forking.
5324-
'child' callbacks are called in order after forking, in the child process.
5325-
'parent' callbacks are called in order after forking, in the parent process.
5342+
'before' callbacks are called in reverse order.
5343+
'after_in_child' and 'after_in_parent' callbacks are called in order.
53265344
53275345
[clinic start generated code]*/
53285346

53295347
static PyObject *
5330-
os_register_at_fork_impl(PyObject *module, PyObject *func, const char *when)
5331-
/*[clinic end generated code: output=8943be81a644750c input=5fc05efa4d42eb84]*/
5348+
os_register_at_fork_impl(PyObject *module, PyObject *before,
5349+
PyObject *after_in_child, PyObject *after_in_parent)
5350+
/*[clinic end generated code: output=5398ac75e8e97625 input=cd1187aa85d2312e]*/
53325351
{
53335352
PyInterpreterState *interp;
5334-
PyObject **lst;
53355353

5336-
if (!PyCallable_Check(func)) {
5337-
PyErr_Format(PyExc_TypeError,
5338-
"expected callable object, got %R", Py_TYPE(func));
5354+
if (!before && !after_in_child && !after_in_parent) {
5355+
PyErr_SetString(PyExc_TypeError, "At least one argument is required.");
5356+
return NULL;
5357+
}
5358+
if (check_null_or_callable(before, "before") ||
5359+
check_null_or_callable(after_in_child, "after_in_child") ||
5360+
check_null_or_callable(after_in_parent, "after_in_parent")) {
53395361
return NULL;
53405362
}
53415363
interp = PyThreadState_Get()->interp;
53425364

5343-
if (!strcmp(when, "before"))
5344-
lst = &interp->before_forkers;
5345-
else if (!strcmp(when, "child"))
5346-
lst = &interp->after_forkers_child;
5347-
else if (!strcmp(when, "parent"))
5348-
lst = &interp->after_forkers_parent;
5349-
else {
5350-
PyErr_Format(PyExc_ValueError, "unexpected value for `when`: '%s'",
5351-
when);
5365+
if (register_at_forker(&interp->before_forkers, before)) {
53525366
return NULL;
53535367
}
5354-
if (register_at_forker(lst, func))
5368+
if (register_at_forker(&interp->after_forkers_child, after_in_child)) {
53555369
return NULL;
5356-
else
5357-
Py_RETURN_NONE;
5370+
}
5371+
if (register_at_forker(&interp->after_forkers_parent, after_in_parent)) {
5372+
return NULL;
5373+
}
5374+
Py_RETURN_NONE;
53585375
}
53595376
#endif /* HAVE_FORK */
53605377

0 commit comments

Comments
 (0)