mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +00:00
gh-104812: Run Pending Calls in any Thread (gh-104813)
For a while now, pending calls only run in the main thread (in the main interpreter). This PR changes things to allow any thread run a pending call, unless the pending call was explicitly added for the main thread to run.
This commit is contained in:
parent
4e80082723
commit
757b402ea1
16 changed files with 766 additions and 123 deletions
|
@ -13,16 +13,18 @@
|
|||
|
||||
#include "Python.h"
|
||||
#include "frameobject.h"
|
||||
#include "interpreteridobject.h" // _PyInterpreterID_LookUp()
|
||||
#include "pycore_atomic_funcs.h" // _Py_atomic_int_get()
|
||||
#include "pycore_bitutils.h" // _Py_bswap32()
|
||||
#include "pycore_compile.h" // _PyCompile_CodeGen, _PyCompile_OptimizeCfg, _PyCompile_Assemble
|
||||
#include "pycore_ceval.h" // _PyEval_AddPendingCall
|
||||
#include "pycore_fileutils.h" // _Py_normpath
|
||||
#include "pycore_frame.h" // _PyInterpreterFrame
|
||||
#include "pycore_gc.h" // PyGC_Head
|
||||
#include "pycore_hashtable.h" // _Py_hashtable_new()
|
||||
#include "pycore_initconfig.h" // _Py_GetConfigsAsDict()
|
||||
#include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal()
|
||||
#include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy()
|
||||
#include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal()
|
||||
#include "pycore_pyerrors.h" // _Py_UTF8_Edit_Cost()
|
||||
#include "pycore_pystate.h" // _PyThreadState_GET()
|
||||
#include "osdefs.h" // MAXPATHLEN
|
||||
|
@ -838,6 +840,120 @@ set_optimizer(PyObject *self, PyObject *opt)
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static int _pending_callback(void *arg)
|
||||
{
|
||||
/* we assume the argument is callable object to which we own a reference */
|
||||
PyObject *callable = (PyObject *)arg;
|
||||
PyObject *r = PyObject_CallNoArgs(callable);
|
||||
Py_DECREF(callable);
|
||||
Py_XDECREF(r);
|
||||
return r != NULL ? 0 : -1;
|
||||
}
|
||||
|
||||
/* The following requests n callbacks to _pending_callback. It can be
|
||||
* run from any python thread.
|
||||
*/
|
||||
static PyObject *
|
||||
pending_threadfunc(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
PyObject *callable;
|
||||
int ensure_added = 0;
|
||||
static char *kwlist[] = {"", "ensure_added", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
|
||||
"O|$p:pending_threadfunc", kwlist,
|
||||
&callable, &ensure_added))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
PyInterpreterState *interp = PyInterpreterState_Get();
|
||||
|
||||
/* create the reference for the callbackwhile we hold the lock */
|
||||
Py_INCREF(callable);
|
||||
|
||||
int r;
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
r = _PyEval_AddPendingCall(interp, &_pending_callback, callable, 0);
|
||||
Py_END_ALLOW_THREADS
|
||||
if (r < 0) {
|
||||
/* unsuccessful add */
|
||||
if (!ensure_added) {
|
||||
Py_DECREF(callable);
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
do {
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
r = _PyEval_AddPendingCall(interp, &_pending_callback, callable, 0);
|
||||
Py_END_ALLOW_THREADS
|
||||
} while (r < 0);
|
||||
}
|
||||
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
|
||||
|
||||
static struct {
|
||||
int64_t interpid;
|
||||
} pending_identify_result;
|
||||
|
||||
static int
|
||||
_pending_identify_callback(void *arg)
|
||||
{
|
||||
PyThread_type_lock mutex = (PyThread_type_lock)arg;
|
||||
assert(pending_identify_result.interpid == -1);
|
||||
PyThreadState *tstate = PyThreadState_Get();
|
||||
pending_identify_result.interpid = PyInterpreterState_GetID(tstate->interp);
|
||||
PyThread_release_lock(mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pending_identify(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *interpid;
|
||||
if (!PyArg_ParseTuple(args, "O:pending_identify", &interpid)) {
|
||||
return NULL;
|
||||
}
|
||||
PyInterpreterState *interp = _PyInterpreterID_LookUp(interpid);
|
||||
if (interp == NULL) {
|
||||
if (!PyErr_Occurred()) {
|
||||
PyErr_SetString(PyExc_ValueError, "interpreter not found");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pending_identify_result.interpid = -1;
|
||||
|
||||
PyThread_type_lock mutex = PyThread_allocate_lock();
|
||||
if (mutex == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
PyThread_acquire_lock(mutex, WAIT_LOCK);
|
||||
/* It gets released in _pending_identify_callback(). */
|
||||
|
||||
int r;
|
||||
do {
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
r = _PyEval_AddPendingCall(interp,
|
||||
&_pending_identify_callback, (void *)mutex,
|
||||
0);
|
||||
Py_END_ALLOW_THREADS
|
||||
} while (r < 0);
|
||||
|
||||
/* Wait for the pending call to complete. */
|
||||
PyThread_acquire_lock(mutex, WAIT_LOCK);
|
||||
PyThread_release_lock(mutex);
|
||||
PyThread_free_lock(mutex);
|
||||
|
||||
PyObject *res = PyLong_FromLongLong(pending_identify_result.interpid);
|
||||
pending_identify_result.interpid = -1;
|
||||
if (res == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef module_functions[] = {
|
||||
{"get_configs", get_configs, METH_NOARGS},
|
||||
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
|
||||
|
@ -868,6 +984,10 @@ static PyMethodDef module_functions[] = {
|
|||
{"iframe_getlasti", iframe_getlasti, METH_O, NULL},
|
||||
{"set_optimizer", set_optimizer, METH_O, NULL},
|
||||
{"get_counter_optimizer", get_counter_optimizer, METH_NOARGS, NULL},
|
||||
{"pending_threadfunc", _PyCFunction_CAST(pending_threadfunc),
|
||||
METH_VARARGS | METH_KEYWORDS},
|
||||
// {"pending_fd_identify", pending_fd_identify, METH_VARARGS, NULL},
|
||||
{"pending_identify", pending_identify, METH_VARARGS, NULL},
|
||||
{NULL, NULL} /* sentinel */
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue