mirror of
https://github.com/python/cpython.git
synced 2025-10-17 04:08:28 +00:00
gh-59956: Allow the "Trashcan" Mechanism to Work Without a Thread State (gh-101209)
We've factored out a struct from the two PyThreadState fields. This accomplishes two things: * make it clear that the trashcan-related code doesn't need any other parts of PyThreadState * allows us to use the trashcan mechanism even when there isn't a "current" thread state We still expect the caller to hold the GIL. https://github.com/python/cpython/issues/59956
This commit is contained in:
parent
984387f39a
commit
7b20a0f55a
5 changed files with 92 additions and 24 deletions
|
@ -507,7 +507,7 @@ PyAPI_FUNC(int) _PyTrash_cond(PyObject *op, destructor dealloc);
|
||||||
/* If "cond" is false, then _tstate remains NULL and the deallocator \
|
/* If "cond" is false, then _tstate remains NULL and the deallocator \
|
||||||
* is run normally without involving the trashcan */ \
|
* is run normally without involving the trashcan */ \
|
||||||
if (cond) { \
|
if (cond) { \
|
||||||
_tstate = PyThreadState_Get(); \
|
_tstate = _PyThreadState_UncheckedGet(); \
|
||||||
if (_PyTrash_begin(_tstate, _PyObject_CAST(op))) { \
|
if (_PyTrash_begin(_tstate, _PyObject_CAST(op))) { \
|
||||||
break; \
|
break; \
|
||||||
} \
|
} \
|
||||||
|
|
|
@ -107,6 +107,11 @@ typedef struct _stack_chunk {
|
||||||
PyObject * data[1]; /* Variable sized */
|
PyObject * data[1]; /* Variable sized */
|
||||||
} _PyStackChunk;
|
} _PyStackChunk;
|
||||||
|
|
||||||
|
struct _py_trashcan {
|
||||||
|
int delete_nesting;
|
||||||
|
PyObject *delete_later;
|
||||||
|
};
|
||||||
|
|
||||||
struct _ts {
|
struct _ts {
|
||||||
/* See Python/ceval.c for comments explaining most fields */
|
/* See Python/ceval.c for comments explaining most fields */
|
||||||
|
|
||||||
|
@ -160,8 +165,7 @@ struct _ts {
|
||||||
*/
|
*/
|
||||||
unsigned long native_thread_id;
|
unsigned long native_thread_id;
|
||||||
|
|
||||||
int trash_delete_nesting;
|
struct _py_trashcan trash;
|
||||||
PyObject *trash_delete_later;
|
|
||||||
|
|
||||||
/* Called when a thread state is deleted normally, but not when it
|
/* Called when a thread state is deleted normally, but not when it
|
||||||
* is destroyed after fork().
|
* is destroyed after fork().
|
||||||
|
|
|
@ -126,6 +126,9 @@ typedef struct pyruntimestate {
|
||||||
/* Used for the thread state bound to the current thread. */
|
/* Used for the thread state bound to the current thread. */
|
||||||
Py_tss_t autoTSSkey;
|
Py_tss_t autoTSSkey;
|
||||||
|
|
||||||
|
/* Used instead of PyThreadState.trash when there is not current tstate. */
|
||||||
|
Py_tss_t trashTSSkey;
|
||||||
|
|
||||||
PyWideStringList orig_argv;
|
PyWideStringList orig_argv;
|
||||||
|
|
||||||
struct _parser_runtime_state parser;
|
struct _parser_runtime_state parser;
|
||||||
|
|
|
@ -2225,22 +2225,20 @@ finally:
|
||||||
* object, with refcount 0. Py_DECREF must already have been called on it.
|
* object, with refcount 0. Py_DECREF must already have been called on it.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
_PyTrash_thread_deposit_object(PyObject *op)
|
_PyTrash_thread_deposit_object(struct _py_trashcan *trash, PyObject *op)
|
||||||
{
|
{
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
|
||||||
_PyObject_ASSERT(op, _PyObject_IS_GC(op));
|
_PyObject_ASSERT(op, _PyObject_IS_GC(op));
|
||||||
_PyObject_ASSERT(op, !_PyObject_GC_IS_TRACKED(op));
|
_PyObject_ASSERT(op, !_PyObject_GC_IS_TRACKED(op));
|
||||||
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
|
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
|
||||||
_PyGCHead_SET_PREV(_Py_AS_GC(op), (PyGC_Head*)tstate->trash_delete_later);
|
_PyGCHead_SET_PREV(_Py_AS_GC(op), (PyGC_Head*)trash->delete_later);
|
||||||
tstate->trash_delete_later = op;
|
trash->delete_later = op;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Deallocate all the objects in the gcstate->trash_delete_later list.
|
/* Deallocate all the objects in the gcstate->trash_delete_later list.
|
||||||
* Called when the call-stack unwinds again. */
|
* Called when the call-stack unwinds again. */
|
||||||
static void
|
static void
|
||||||
_PyTrash_thread_destroy_chain(void)
|
_PyTrash_thread_destroy_chain(struct _py_trashcan *trash)
|
||||||
{
|
{
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
|
||||||
/* We need to increase trash_delete_nesting here, otherwise,
|
/* We need to increase trash_delete_nesting here, otherwise,
|
||||||
_PyTrash_thread_destroy_chain will be called recursively
|
_PyTrash_thread_destroy_chain will be called recursively
|
||||||
and then possibly crash. An example that may crash without
|
and then possibly crash. An example that may crash without
|
||||||
|
@ -2252,13 +2250,13 @@ _PyTrash_thread_destroy_chain(void)
|
||||||
tups = [(tup,) for tup in tups]
|
tups = [(tup,) for tup in tups]
|
||||||
del tups
|
del tups
|
||||||
*/
|
*/
|
||||||
assert(tstate->trash_delete_nesting == 0);
|
assert(trash->delete_nesting == 0);
|
||||||
++tstate->trash_delete_nesting;
|
++trash->delete_nesting;
|
||||||
while (tstate->trash_delete_later) {
|
while (trash->delete_later) {
|
||||||
PyObject *op = tstate->trash_delete_later;
|
PyObject *op = trash->delete_later;
|
||||||
destructor dealloc = Py_TYPE(op)->tp_dealloc;
|
destructor dealloc = Py_TYPE(op)->tp_dealloc;
|
||||||
|
|
||||||
tstate->trash_delete_later =
|
trash->delete_later =
|
||||||
(PyObject*) _PyGCHead_PREV(_Py_AS_GC(op));
|
(PyObject*) _PyGCHead_PREV(_Py_AS_GC(op));
|
||||||
|
|
||||||
/* Call the deallocator directly. This used to try to
|
/* Call the deallocator directly. This used to try to
|
||||||
|
@ -2269,22 +2267,64 @@ _PyTrash_thread_destroy_chain(void)
|
||||||
*/
|
*/
|
||||||
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
|
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
|
||||||
(*dealloc)(op);
|
(*dealloc)(op);
|
||||||
assert(tstate->trash_delete_nesting == 1);
|
assert(trash->delete_nesting == 1);
|
||||||
|
}
|
||||||
|
--trash->delete_nesting;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static struct _py_trashcan *
|
||||||
|
_PyTrash_get_state(PyThreadState *tstate)
|
||||||
|
{
|
||||||
|
if (tstate != NULL) {
|
||||||
|
return &tstate->trash;
|
||||||
|
}
|
||||||
|
// The current thread must be finalizing.
|
||||||
|
// Fall back to using thread-local state.
|
||||||
|
// XXX Use thread-local variable syntax?
|
||||||
|
assert(PyThread_tss_is_created(&_PyRuntime.trashTSSkey));
|
||||||
|
struct _py_trashcan *trash =
|
||||||
|
(struct _py_trashcan *)PyThread_tss_get(&_PyRuntime.trashTSSkey);
|
||||||
|
if (trash == NULL) {
|
||||||
|
trash = PyMem_RawMalloc(sizeof(struct _py_trashcan));
|
||||||
|
if (trash == NULL) {
|
||||||
|
Py_FatalError("Out of memory");
|
||||||
|
}
|
||||||
|
PyThread_tss_set(&_PyRuntime.trashTSSkey, (void *)trash);
|
||||||
|
}
|
||||||
|
return trash;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_PyTrash_clear_state(PyThreadState *tstate)
|
||||||
|
{
|
||||||
|
if (tstate != NULL) {
|
||||||
|
assert(tstate->trash.delete_later == NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (PyThread_tss_is_created(&_PyRuntime.trashTSSkey)) {
|
||||||
|
struct _py_trashcan *trash =
|
||||||
|
(struct _py_trashcan *)PyThread_tss_get(&_PyRuntime.trashTSSkey);
|
||||||
|
if (trash != NULL) {
|
||||||
|
PyThread_tss_set(&_PyRuntime.trashTSSkey, (void *)NULL);
|
||||||
|
PyMem_RawFree(trash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
--tstate->trash_delete_nesting;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
_PyTrash_begin(PyThreadState *tstate, PyObject *op)
|
_PyTrash_begin(PyThreadState *tstate, PyObject *op)
|
||||||
{
|
{
|
||||||
if (tstate->trash_delete_nesting >= _PyTrash_UNWIND_LEVEL) {
|
// XXX Make sure the GIL is held.
|
||||||
|
struct _py_trashcan *trash = _PyTrash_get_state(tstate);
|
||||||
|
if (trash->delete_nesting >= _PyTrash_UNWIND_LEVEL) {
|
||||||
/* Store the object (to be deallocated later) and jump past
|
/* Store the object (to be deallocated later) and jump past
|
||||||
* Py_TRASHCAN_END, skipping the body of the deallocator */
|
* Py_TRASHCAN_END, skipping the body of the deallocator */
|
||||||
_PyTrash_thread_deposit_object(op);
|
_PyTrash_thread_deposit_object(trash, op);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
++tstate->trash_delete_nesting;
|
++trash->delete_nesting;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2292,9 +2332,14 @@ _PyTrash_begin(PyThreadState *tstate, PyObject *op)
|
||||||
void
|
void
|
||||||
_PyTrash_end(PyThreadState *tstate)
|
_PyTrash_end(PyThreadState *tstate)
|
||||||
{
|
{
|
||||||
--tstate->trash_delete_nesting;
|
// XXX Make sure the GIL is held.
|
||||||
if (tstate->trash_delete_later && tstate->trash_delete_nesting <= 0) {
|
struct _py_trashcan *trash = _PyTrash_get_state(tstate);
|
||||||
_PyTrash_thread_destroy_chain();
|
--trash->delete_nesting;
|
||||||
|
if (trash->delete_nesting <= 0) {
|
||||||
|
if (trash->delete_later != NULL) {
|
||||||
|
_PyTrash_thread_destroy_chain(trash);
|
||||||
|
}
|
||||||
|
_PyTrash_clear_state(tstate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2371,7 +2416,7 @@ _Py_Dealloc(PyObject *op)
|
||||||
destructor dealloc = type->tp_dealloc;
|
destructor dealloc = type->tp_dealloc;
|
||||||
#ifdef Py_DEBUG
|
#ifdef Py_DEBUG
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
PyObject *old_exc_type = tstate->curexc_type;
|
PyObject *old_exc_type = tstate != NULL ? tstate->curexc_type : NULL;
|
||||||
// Keep the old exception type alive to prevent undefined behavior
|
// Keep the old exception type alive to prevent undefined behavior
|
||||||
// on (tstate->curexc_type != old_exc_type) below
|
// on (tstate->curexc_type != old_exc_type) below
|
||||||
Py_XINCREF(old_exc_type);
|
Py_XINCREF(old_exc_type);
|
||||||
|
@ -2387,7 +2432,7 @@ _Py_Dealloc(PyObject *op)
|
||||||
#ifdef Py_DEBUG
|
#ifdef Py_DEBUG
|
||||||
// gh-89373: The tp_dealloc function must leave the current exception
|
// gh-89373: The tp_dealloc function must leave the current exception
|
||||||
// unchanged.
|
// unchanged.
|
||||||
if (tstate->curexc_type != old_exc_type) {
|
if (tstate != NULL && tstate->curexc_type != old_exc_type) {
|
||||||
const char *err;
|
const char *err;
|
||||||
if (old_exc_type == NULL) {
|
if (old_exc_type == NULL) {
|
||||||
err = "Deallocator of type '%s' raised an exception";
|
err = "Deallocator of type '%s' raised an exception";
|
||||||
|
|
|
@ -400,6 +400,11 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PyThread_tss_create(&runtime->trashTSSkey) != 0) {
|
||||||
|
_PyRuntimeState_Fini(runtime);
|
||||||
|
return _PyStatus_NO_MEMORY();
|
||||||
|
}
|
||||||
|
|
||||||
init_runtime(runtime, open_code_hook, open_code_userdata, audit_hook_head,
|
init_runtime(runtime, open_code_hook, open_code_userdata, audit_hook_head,
|
||||||
unicode_next_index, lock1, lock2, lock3, lock4);
|
unicode_next_index, lock1, lock2, lock3, lock4);
|
||||||
|
|
||||||
|
@ -413,6 +418,10 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
|
||||||
current_tss_fini(runtime);
|
current_tss_fini(runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PyThread_tss_is_created(&runtime->trashTSSkey)) {
|
||||||
|
PyThread_tss_delete(&runtime->trashTSSkey);
|
||||||
|
}
|
||||||
|
|
||||||
/* Force the allocator used by _PyRuntimeState_Init(). */
|
/* Force the allocator used by _PyRuntimeState_Init(). */
|
||||||
PyMemAllocatorEx old_alloc;
|
PyMemAllocatorEx old_alloc;
|
||||||
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
|
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
|
||||||
|
@ -471,6 +480,13 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PyThread_tss_is_created(&runtime->trashTSSkey)) {
|
||||||
|
PyThread_tss_delete(&runtime->trashTSSkey);
|
||||||
|
}
|
||||||
|
if (PyThread_tss_create(&runtime->trashTSSkey) != 0) {
|
||||||
|
return _PyStatus_NO_MEMORY();
|
||||||
|
}
|
||||||
|
|
||||||
return _PyStatus_OK();
|
return _PyStatus_OK();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue