summaryrefslogtreecommitdiffstats
path: root/Modules
diff options
context:
space:
mode:
authorAntoine Pitrou <pitrou@free.fr>2017-05-27 15:50:54 (GMT)
committerGitHub <noreply@github.com>2017-05-27 15:50:54 (GMT)
commit346cbd351ee0dd3ab9cb9f0e4cb625556707877e (patch)
tree8590c5fc85acf57750ecb8d07a407a3dbe233f85 /Modules
parentf931fd1c2ad969db72460d3ab41e3d1a4a62c371 (diff)
downloadcpython-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.c29
-rw-r--r--Modules/clinic/posixmodule.c.h50
-rw-r--r--Modules/posixmodule.c184
-rw-r--r--Modules/signalmodule.c8
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
}