diff options
author | Antoine Pitrou <pitrou@free.fr> | 2017-05-27 15:50:54 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-27 15:50:54 (GMT) |
commit | 346cbd351ee0dd3ab9cb9f0e4cb625556707877e (patch) | |
tree | 8590c5fc85acf57750ecb8d07a407a3dbe233f85 /Modules | |
parent | f931fd1c2ad969db72460d3ab41e3d1a4a62c371 (diff) | |
download | cpython-346cbd351ee0dd3ab9cb9f0e4cb625556707877e.zip cpython-346cbd351ee0dd3ab9cb9f0e4cb625556707877e.tar.gz cpython-346cbd351ee0dd3ab9cb9f0e4cb625556707877e.tar.bz2 |
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
Diffstat (limited to 'Modules')
-rw-r--r-- | Modules/_posixsubprocess.c | 29 | ||||
-rw-r--r-- | Modules/clinic/posixmodule.c.h | 50 | ||||
-rw-r--r-- | Modules/posixmodule.c | 184 | ||||
-rw-r--r-- | Modules/signalmodule.c | 8 |
4 files changed, 212 insertions, 59 deletions
diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c index d1434d5..5228fec 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -559,9 +559,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args) int need_to_reenable_gc = 0; char *const *exec_array, *const *argv = NULL, *const *envp = NULL; Py_ssize_t arg_num; -#ifdef WITH_THREAD - int import_lock_held = 0; -#endif + int need_after_fork = 0; if (!PyArg_ParseTuple( args, "OOpO!OOiiiiiiiiiiO:fork_exec", @@ -657,10 +655,8 @@ subprocess_fork_exec(PyObject* self, PyObject *args) preexec_fn_args_tuple = PyTuple_New(0); if (!preexec_fn_args_tuple) goto cleanup; -#ifdef WITH_THREAD - _PyImport_AcquireLock(); - import_lock_held = 1; -#endif + PyOS_BeforeFork(); + need_after_fork = 1; } 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 * back into Python. The user asked us to use hope as a strategy * to avoid deadlock... */ - PyOS_AfterFork(); + PyOS_AfterFork_Child(); } 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. */ 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 */ + if (need_after_fork) + PyOS_AfterFork_Parent(); if (envp) _Py_FreeCharPArray(envp); if (argv) @@ -733,10 +722,8 @@ subprocess_fork_exec(PyObject* self, PyObject *args) return PyLong_FromPid(pid); cleanup: -#ifdef WITH_THREAD - if (import_lock_held) - _PyImport_ReleaseLock(); -#endif + if (need_after_fork) + PyOS_AfterFork_Parent(); if (envp) _Py_FreeCharPArray(envp); if (argv) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 6ef0293..2c919e1 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -1825,6 +1825,50 @@ exit: #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) PyDoc_STRVAR(os_fork1__doc__, @@ -6122,6 +6166,10 @@ exit: #define 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 #define OS_FORK1_METHODDEF #endif /* !defined(OS_FORK1_METHODDEF) */ @@ -6493,4 +6541,4 @@ exit: #ifndef OS_GETRANDOM_METHODDEF #define OS_GETRANDOM_METHODDEF #endif /* !defined(OS_GETRANDOM_METHODDEF) */ -/*[clinic end generated code: output=5529857101c08b49 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=699e11c5579a104e input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 5c73918..be8a66d 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -25,6 +25,7 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" +#include "pythread.h" #include "structmember.h" #ifndef MS_WINDOWS #include "posixmodule.h" @@ -394,6 +395,95 @@ static int win32_can_symlink = 0; #define MODNAME "posix" #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 /* defined in fileutils.c */ 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 */ +#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 /*[clinic input] os.fork1 @@ -5232,24 +5373,18 @@ os_fork1_impl(PyObject *module) /*[clinic end generated code: output=0de8e67ce2a310bc input=12db02167893926e]*/ { pid_t pid; - int result = 0; - _PyImport_AcquireLock(); + + PyOS_BeforeFork(); pid = fork1(); if (pid == 0) { /* child: this clobbers and resets the import lock. */ - PyOS_AfterFork(); + PyOS_AfterFork_Child(); } else { /* parent: release the import lock. */ - result = _PyImport_ReleaseLock(); + PyOS_AfterFork_Parent(); } if (pid == -1) 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); } #endif /* HAVE_FORK1 */ @@ -5269,24 +5404,18 @@ os_fork_impl(PyObject *module) /*[clinic end generated code: output=3626c81f98985d49 input=13c956413110eeaa]*/ { pid_t pid; - int result = 0; - _PyImport_AcquireLock(); + + PyOS_BeforeFork(); pid = fork(); if (pid == 0) { /* child: this clobbers and resets the import lock. */ - PyOS_AfterFork(); + PyOS_AfterFork_Child(); } else { /* parent: release the import lock. */ - result = _PyImport_ReleaseLock(); + PyOS_AfterFork_Parent(); } if (pid == -1) 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); } #endif /* HAVE_FORK */ @@ -5868,26 +5997,20 @@ static PyObject * os_forkpty_impl(PyObject *module) /*[clinic end generated code: output=60d0a5c7512e4087 input=f1f7f4bae3966010]*/ { - int master_fd = -1, result = 0; + int master_fd = -1; pid_t pid; - _PyImport_AcquireLock(); + PyOS_BeforeFork(); pid = forkpty(&master_fd, NULL, NULL, NULL); if (pid == 0) { /* child: this clobbers and resets the import lock. */ - PyOS_AfterFork(); + PyOS_AfterFork_Child(); } else { /* parent: release the import lock. */ - result = _PyImport_ReleaseLock(); + PyOS_AfterFork_Parent(); } if (pid == -1) 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); } #endif /* HAVE_FORKPTY */ @@ -12265,6 +12388,7 @@ static PyMethodDef posix_methods[] = { OS_SPAWNVE_METHODDEF OS_FORK1_METHODDEF OS_FORK_METHODDEF + OS_REGISTER_AT_FORK_METHODDEF OS_SCHED_GET_PRIORITY_MAX_METHODDEF OS_SCHED_GET_PRIORITY_MIN_METHODDEF OS_SCHED_GETPARAM_METHODDEF diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index 108832b..75abc98 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -1618,21 +1618,15 @@ _clear_pending_signals(void) } void -PyOS_AfterFork(void) +_PySignal_AfterFork(void) { /* 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 * the interpreter had an opportunity to call the handlers. issue9535. */ _clear_pending_signals(); #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_pid = getpid(); - _PyImport_ReInitLock(); #endif } |