Skip to content

Commit 7b20a0f

Browse files
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. #59956
1 parent 984387f commit 7b20a0f

File tree

5 files changed

+92
-24
lines changed

5 files changed

+92
-24
lines changed

Include/cpython/object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ PyAPI_FUNC(int) _PyTrash_cond(PyObject *op, destructor dealloc);
507507
/* If "cond" is false, then _tstate remains NULL and the deallocator \
508508
* is run normally without involving the trashcan */ \
509509
if (cond) { \
510-
_tstate = PyThreadState_Get(); \
510+
_tstate = _PyThreadState_UncheckedGet(); \
511511
if (_PyTrash_begin(_tstate, _PyObject_CAST(op))) { \
512512
break; \
513513
} \

Include/cpython/pystate.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ typedef struct _stack_chunk {
107107
PyObject * data[1]; /* Variable sized */
108108
} _PyStackChunk;
109109

110+
struct _py_trashcan {
111+
int delete_nesting;
112+
PyObject *delete_later;
113+
};
114+
110115
struct _ts {
111116
/* See Python/ceval.c for comments explaining most fields */
112117

@@ -160,8 +165,7 @@ struct _ts {
160165
*/
161166
unsigned long native_thread_id;
162167

163-
int trash_delete_nesting;
164-
PyObject *trash_delete_later;
168+
struct _py_trashcan trash;
165169

166170
/* Called when a thread state is deleted normally, but not when it
167171
* is destroyed after fork().

Include/internal/pycore_runtime.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ typedef struct pyruntimestate {
126126
/* Used for the thread state bound to the current thread. */
127127
Py_tss_t autoTSSkey;
128128

129+
/* Used instead of PyThreadState.trash when there is not current tstate. */
130+
Py_tss_t trashTSSkey;
131+
129132
PyWideStringList orig_argv;
130133

131134
struct _parser_runtime_state parser;

Objects/object.c

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2225,22 +2225,20 @@ Py_ReprLeave(PyObject *obj)
22252225
* object, with refcount 0. Py_DECREF must already have been called on it.
22262226
*/
22272227
static void
2228-
_PyTrash_thread_deposit_object(PyObject *op)
2228+
_PyTrash_thread_deposit_object(struct _py_trashcan *trash, PyObject *op)
22292229
{
2230-
PyThreadState *tstate = _PyThreadState_GET();
22312230
_PyObject_ASSERT(op, _PyObject_IS_GC(op));
22322231
_PyObject_ASSERT(op, !_PyObject_GC_IS_TRACKED(op));
22332232
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
2234-
_PyGCHead_SET_PREV(_Py_AS_GC(op), (PyGC_Head*)tstate->trash_delete_later);
2235-
tstate->trash_delete_later = op;
2233+
_PyGCHead_SET_PREV(_Py_AS_GC(op), (PyGC_Head*)trash->delete_later);
2234+
trash->delete_later = op;
22362235
}
22372236

22382237
/* Deallocate all the objects in the gcstate->trash_delete_later list.
22392238
* Called when the call-stack unwinds again. */
22402239
static void
2241-
_PyTrash_thread_destroy_chain(void)
2240+
_PyTrash_thread_destroy_chain(struct _py_trashcan *trash)
22422241
{
2243-
PyThreadState *tstate = _PyThreadState_GET();
22442242
/* We need to increase trash_delete_nesting here, otherwise,
22452243
_PyTrash_thread_destroy_chain will be called recursively
22462244
and then possibly crash. An example that may crash without
@@ -2252,13 +2250,13 @@ _PyTrash_thread_destroy_chain(void)
22522250
tups = [(tup,) for tup in tups]
22532251
del tups
22542252
*/
2255-
assert(tstate->trash_delete_nesting == 0);
2256-
++tstate->trash_delete_nesting;
2257-
while (tstate->trash_delete_later) {
2258-
PyObject *op = tstate->trash_delete_later;
2253+
assert(trash->delete_nesting == 0);
2254+
++trash->delete_nesting;
2255+
while (trash->delete_later) {
2256+
PyObject *op = trash->delete_later;
22592257
destructor dealloc = Py_TYPE(op)->tp_dealloc;
22602258

2261-
tstate->trash_delete_later =
2259+
trash->delete_later =
22622260
(PyObject*) _PyGCHead_PREV(_Py_AS_GC(op));
22632261

22642262
/* Call the deallocator directly. This used to try to
@@ -2269,32 +2267,79 @@ _PyTrash_thread_destroy_chain(void)
22692267
*/
22702268
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
22712269
(*dealloc)(op);
2272-
assert(tstate->trash_delete_nesting == 1);
2270+
assert(trash->delete_nesting == 1);
2271+
}
2272+
--trash->delete_nesting;
2273+
}
2274+
2275+
2276+
static struct _py_trashcan *
2277+
_PyTrash_get_state(PyThreadState *tstate)
2278+
{
2279+
if (tstate != NULL) {
2280+
return &tstate->trash;
2281+
}
2282+
// The current thread must be finalizing.
2283+
// Fall back to using thread-local state.
2284+
// XXX Use thread-local variable syntax?
2285+
assert(PyThread_tss_is_created(&_PyRuntime.trashTSSkey));
2286+
struct _py_trashcan *trash =
2287+
(struct _py_trashcan *)PyThread_tss_get(&_PyRuntime.trashTSSkey);
2288+
if (trash == NULL) {
2289+
trash = PyMem_RawMalloc(sizeof(struct _py_trashcan));
2290+
if (trash == NULL) {
2291+
Py_FatalError("Out of memory");
2292+
}
2293+
PyThread_tss_set(&_PyRuntime.trashTSSkey, (void *)trash);
2294+
}
2295+
return trash;
2296+
}
2297+
2298+
static void
2299+
_PyTrash_clear_state(PyThreadState *tstate)
2300+
{
2301+
if (tstate != NULL) {
2302+
assert(tstate->trash.delete_later == NULL);
2303+
return;
2304+
}
2305+
if (PyThread_tss_is_created(&_PyRuntime.trashTSSkey)) {
2306+
struct _py_trashcan *trash =
2307+
(struct _py_trashcan *)PyThread_tss_get(&_PyRuntime.trashTSSkey);
2308+
if (trash != NULL) {
2309+
PyThread_tss_set(&_PyRuntime.trashTSSkey, (void *)NULL);
2310+
PyMem_RawFree(trash);
2311+
}
22732312
}
2274-
--tstate->trash_delete_nesting;
22752313
}
22762314

22772315

22782316
int
22792317
_PyTrash_begin(PyThreadState *tstate, PyObject *op)
22802318
{
2281-
if (tstate->trash_delete_nesting >= _PyTrash_UNWIND_LEVEL) {
2319+
// XXX Make sure the GIL is held.
2320+
struct _py_trashcan *trash = _PyTrash_get_state(tstate);
2321+
if (trash->delete_nesting >= _PyTrash_UNWIND_LEVEL) {
22822322
/* Store the object (to be deallocated later) and jump past
22832323
* Py_TRASHCAN_END, skipping the body of the deallocator */
2284-
_PyTrash_thread_deposit_object(op);
2324+
_PyTrash_thread_deposit_object(trash, op);
22852325
return 1;
22862326
}
2287-
++tstate->trash_delete_nesting;
2327+
++trash->delete_nesting;
22882328
return 0;
22892329
}
22902330

22912331

22922332
void
22932333
_PyTrash_end(PyThreadState *tstate)
22942334
{
2295-
--tstate->trash_delete_nesting;
2296-
if (tstate->trash_delete_later && tstate->trash_delete_nesting <= 0) {
2297-
_PyTrash_thread_destroy_chain();
2335+
// XXX Make sure the GIL is held.
2336+
struct _py_trashcan *trash = _PyTrash_get_state(tstate);
2337+
--trash->delete_nesting;
2338+
if (trash->delete_nesting <= 0) {
2339+
if (trash->delete_later != NULL) {
2340+
_PyTrash_thread_destroy_chain(trash);
2341+
}
2342+
_PyTrash_clear_state(tstate);
22982343
}
22992344
}
23002345

@@ -2371,7 +2416,7 @@ _Py_Dealloc(PyObject *op)
23712416
destructor dealloc = type->tp_dealloc;
23722417
#ifdef Py_DEBUG
23732418
PyThreadState *tstate = _PyThreadState_GET();
2374-
PyObject *old_exc_type = tstate->curexc_type;
2419+
PyObject *old_exc_type = tstate != NULL ? tstate->curexc_type : NULL;
23752420
// Keep the old exception type alive to prevent undefined behavior
23762421
// on (tstate->curexc_type != old_exc_type) below
23772422
Py_XINCREF(old_exc_type);
@@ -2387,7 +2432,7 @@ _Py_Dealloc(PyObject *op)
23872432
#ifdef Py_DEBUG
23882433
// gh-89373: The tp_dealloc function must leave the current exception
23892434
// unchanged.
2390-
if (tstate->curexc_type != old_exc_type) {
2435+
if (tstate != NULL && tstate->curexc_type != old_exc_type) {
23912436
const char *err;
23922437
if (old_exc_type == NULL) {
23932438
err = "Deallocator of type '%s' raised an exception";

Python/pystate.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,11 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
400400
return status;
401401
}
402402

403+
if (PyThread_tss_create(&runtime->trashTSSkey) != 0) {
404+
_PyRuntimeState_Fini(runtime);
405+
return _PyStatus_NO_MEMORY();
406+
}
407+
403408
init_runtime(runtime, open_code_hook, open_code_userdata, audit_hook_head,
404409
unicode_next_index, lock1, lock2, lock3, lock4);
405410

@@ -413,6 +418,10 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
413418
current_tss_fini(runtime);
414419
}
415420

421+
if (PyThread_tss_is_created(&runtime->trashTSSkey)) {
422+
PyThread_tss_delete(&runtime->trashTSSkey);
423+
}
424+
416425
/* Force the allocator used by _PyRuntimeState_Init(). */
417426
PyMemAllocatorEx old_alloc;
418427
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
@@ -471,6 +480,13 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
471480
return status;
472481
}
473482

483+
if (PyThread_tss_is_created(&runtime->trashTSSkey)) {
484+
PyThread_tss_delete(&runtime->trashTSSkey);
485+
}
486+
if (PyThread_tss_create(&runtime->trashTSSkey) != 0) {
487+
return _PyStatus_NO_MEMORY();
488+
}
489+
474490
return _PyStatus_OK();
475491
}
476492
#endif

0 commit comments

Comments
 (0)