mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
Issue #14432: Generator now clears the borrowed reference to the thread state
Fix a crash when a generator is created in a C thread that is destroyed while the generator is still used. The issue was that a generator contains a frame, and the frame kept a reference to the Python state of the destroyed C thread. The crash occurs when a trace function is setup.
This commit is contained in:
parent
da12adac10
commit
1310510793
4 changed files with 141 additions and 0 deletions
|
@ -2477,6 +2477,93 @@ test_pytime_object_to_timespec(PyObject *self, PyObject *args)
|
|||
return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), nsec);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
PyThread_type_lock start_event;
|
||||
PyThread_type_lock exit_event;
|
||||
PyObject *callback;
|
||||
} test_c_thread_t;
|
||||
|
||||
static void
|
||||
temporary_c_thread(void *data)
|
||||
{
|
||||
test_c_thread_t *test_c_thread = data;
|
||||
PyGILState_STATE state;
|
||||
PyObject *res;
|
||||
|
||||
PyThread_release_lock(test_c_thread->start_event);
|
||||
|
||||
/* Allocate a Python thread state for this thread */
|
||||
state = PyGILState_Ensure();
|
||||
|
||||
res = PyObject_CallFunction(test_c_thread->callback, "", NULL);
|
||||
Py_CLEAR(test_c_thread->callback);
|
||||
|
||||
if (res == NULL) {
|
||||
PyErr_Print();
|
||||
}
|
||||
else {
|
||||
Py_DECREF(res);
|
||||
}
|
||||
|
||||
/* Destroy the Python thread state for this thread */
|
||||
PyGILState_Release(state);
|
||||
|
||||
PyThread_release_lock(test_c_thread->exit_event);
|
||||
|
||||
PyThread_exit_thread();
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
call_in_temporary_c_thread(PyObject *self, PyObject *callback)
|
||||
{
|
||||
PyObject *res = NULL;
|
||||
test_c_thread_t test_c_thread;
|
||||
long thread;
|
||||
|
||||
PyEval_InitThreads();
|
||||
|
||||
test_c_thread.start_event = PyThread_allocate_lock();
|
||||
test_c_thread.exit_event = PyThread_allocate_lock();
|
||||
test_c_thread.callback = NULL;
|
||||
if (!test_c_thread.start_event || !test_c_thread.exit_event) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "could not allocate lock");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
Py_INCREF(callback);
|
||||
test_c_thread.callback = callback;
|
||||
|
||||
PyThread_acquire_lock(test_c_thread.start_event, 1);
|
||||
PyThread_acquire_lock(test_c_thread.exit_event, 1);
|
||||
|
||||
thread = PyThread_start_new_thread(temporary_c_thread, &test_c_thread);
|
||||
if (thread == -1) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "unable to start the thread");
|
||||
PyThread_release_lock(test_c_thread.start_event);
|
||||
PyThread_release_lock(test_c_thread.exit_event);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
PyThread_acquire_lock(test_c_thread.start_event, 1);
|
||||
PyThread_release_lock(test_c_thread.start_event);
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
PyThread_acquire_lock(test_c_thread.exit_event, 1);
|
||||
PyThread_release_lock(test_c_thread.exit_event);
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
res = Py_None;
|
||||
|
||||
exit:
|
||||
Py_CLEAR(test_c_thread.callback);
|
||||
if (test_c_thread.start_event)
|
||||
PyThread_free_lock(test_c_thread.start_event);
|
||||
if (test_c_thread.exit_event)
|
||||
PyThread_free_lock(test_c_thread.exit_event);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef TestMethods[] = {
|
||||
{"raise_exception", raise_exception, METH_VARARGS},
|
||||
|
@ -2574,6 +2661,8 @@ static PyMethodDef TestMethods[] = {
|
|||
{"pytime_object_to_time_t", test_pytime_object_to_time_t, METH_VARARGS},
|
||||
{"pytime_object_to_timeval", test_pytime_object_to_timeval, METH_VARARGS},
|
||||
{"pytime_object_to_timespec", test_pytime_object_to_timespec, METH_VARARGS},
|
||||
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_O,
|
||||
PyDoc_STR("set_error_class(error_class) -> None")},
|
||||
{NULL, NULL} /* sentinel */
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue