Skip to content

Commit 29e2a03

Browse files
committed
Streamline the trashcan mechanism
1 parent 02f1385 commit 29e2a03

File tree

4 files changed

+37
-123
lines changed

4 files changed

+37
-123
lines changed

Include/cpython/object.h

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -461,30 +461,29 @@ passed as second argument to Py_TRASHCAN_BEGIN().
461461
/* Python 3.9 private API, invoked by the macros below. */
462462
PyAPI_FUNC(int) _PyTrash_begin(PyThreadState *tstate, PyObject *op);
463463
PyAPI_FUNC(void) _PyTrash_end(PyThreadState *tstate);
464+
465+
PyAPI_FUNC(void) PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op);
466+
PyAPI_FUNC(void) PyTrash_thread_destroy_chain(PyThreadState *tstate);
467+
468+
464469
/* Python 3.10 private API, invoked by the Py_TRASHCAN_BEGIN(). */
465-
PyAPI_FUNC(int) _PyTrash_cond(PyObject *op, destructor dealloc);
466470

467-
#define Py_TRASHCAN_BEGIN_CONDITION(op, cond) \
468-
do { \
469-
PyThreadState *_tstate = NULL; \
470-
/* If "cond" is false, then _tstate remains NULL and the deallocator \
471-
* is run normally without involving the trashcan */ \
472-
if (cond) { \
473-
_tstate = PyThreadState_GetUnchecked(); \
474-
if (_PyTrash_begin(_tstate, _PyObject_CAST(op))) { \
475-
break; \
476-
} \
477-
}
478-
/* The body of the deallocator is here. */
471+
#define Py_TRASHCAN_BEGIN(op, dealloc) \
472+
do { \
473+
PyThreadState *tstate = PyThreadState_Get(); \
474+
if (tstate->c_recursion_remaining <= 0 && Py_TYPE(op)->tp_dealloc == (destructor)dealloc) { \
475+
PyTrash_thread_deposit_object(tstate, (PyObject *)op); \
476+
break; \
477+
} \
478+
tstate->c_recursion_remaining--;
479+
/* The body of the deallocator is here. */
479480
#define Py_TRASHCAN_END \
480-
if (_tstate) { \
481-
_PyTrash_end(_tstate); \
482-
} \
483-
} while (0);
481+
tstate->c_recursion_remaining++; \
482+
if (tstate->delete_later && tstate->c_recursion_remaining > 50) { \
483+
PyTrash_thread_destroy_chain(tstate); \
484+
} \
485+
} while (0);
484486

485-
#define Py_TRASHCAN_BEGIN(op, dealloc) \
486-
Py_TRASHCAN_BEGIN_CONDITION((op), \
487-
_PyTrash_cond(_PyObject_CAST(op), (destructor)(dealloc)))
488487

489488

490489
PyAPI_FUNC(void *) PyObject_GetItemData(PyObject *obj);

Include/cpython/pystate.h

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,6 @@ typedef struct _stack_chunk {
5656
PyObject * data[1]; /* Variable sized */
5757
} _PyStackChunk;
5858

59-
struct _py_trashcan {
60-
int delete_nesting;
61-
PyObject *delete_later;
62-
};
63-
6459
struct _ts {
6560
/* See Python/ceval.c for comments explaining most fields */
6661

@@ -152,7 +147,7 @@ struct _ts {
152147
*/
153148
unsigned long native_thread_id;
154149

155-
struct _py_trashcan trash;
150+
PyObject *delete_later;
156151

157152
/* Tagged pointer to top-most critical section, or zero if there is no
158153
* active critical section. Critical sections are only used in

Objects/object.c

Lines changed: 15 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -2692,27 +2692,27 @@ Py_ReprLeave(PyObject *obj)
26922692
* call-stack depth gets large. op must be a currently untracked gc'ed
26932693
* object, with refcount 0. Py_DECREF must already have been called on it.
26942694
*/
2695-
static void
2696-
_PyTrash_thread_deposit_object(struct _py_trashcan *trash, PyObject *op)
2695+
void
2696+
PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op)
26972697
{
26982698
_PyObject_ASSERT(op, _PyObject_IS_GC(op));
26992699
_PyObject_ASSERT(op, !_PyObject_GC_IS_TRACKED(op));
27002700
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
27012701
#ifdef Py_GIL_DISABLED
27022702
_PyObject_ASSERT(op, op->ob_tid == 0);
2703-
op->ob_tid = (uintptr_t)trash->delete_later;
2703+
op->ob_tid = (uintptr_t)tstate->delete_later;
27042704
#else
2705-
_PyGCHead_SET_PREV(_Py_AS_GC(op), (PyGC_Head*)trash->delete_later);
2705+
_PyGCHead_SET_PREV(_Py_AS_GC(op), (PyGC_Head*)tstate->delete_later);
27062706
#endif
2707-
trash->delete_later = op;
2707+
tstate->delete_later = op;
27082708
}
27092709

27102710
/* Deallocate all the objects in the gcstate->trash_delete_later list.
27112711
* Called when the call-stack unwinds again. */
2712-
static void
2713-
_PyTrash_thread_destroy_chain(struct _py_trashcan *trash)
2712+
void
2713+
PyTrash_thread_destroy_chain(PyThreadState *tstate)
27142714
{
2715-
/* We need to increase trash_delete_nesting here, otherwise,
2715+
/* We need to increase c_recursion_remaining here, otherwise,
27162716
_PyTrash_thread_destroy_chain will be called recursively
27172717
and then possibly crash. An example that may crash without
27182718
increase:
@@ -2723,17 +2723,17 @@ _PyTrash_thread_destroy_chain(struct _py_trashcan *trash)
27232723
tups = [(tup,) for tup in tups]
27242724
del tups
27252725
*/
2726-
assert(trash->delete_nesting == 0);
2727-
++trash->delete_nesting;
2728-
while (trash->delete_later) {
2729-
PyObject *op = trash->delete_later;
2726+
assert(tstate->c_recursion_remaining > 50);
2727+
tstate->c_recursion_remaining--;
2728+
while (tstate->delete_later) {
2729+
PyObject *op = tstate->delete_later;
27302730
destructor dealloc = Py_TYPE(op)->tp_dealloc;
27312731

27322732
#ifdef Py_GIL_DISABLED
2733-
trash->delete_later = (PyObject*) op->ob_tid;
2733+
tstate->delete_later = (PyObject*) op->ob_tid;
27342734
op->ob_tid = 0;
27352735
#else
2736-
trash->delete_later = (PyObject*) _PyGCHead_PREV(_Py_AS_GC(op));
2736+
tstate->delete_later = (PyObject*) _PyGCHead_PREV(_Py_AS_GC(op));
27372737
#endif
27382738

27392739
/* Call the deallocator directly. This used to try to
@@ -2744,92 +2744,10 @@ _PyTrash_thread_destroy_chain(struct _py_trashcan *trash)
27442744
*/
27452745
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
27462746
(*dealloc)(op);
2747-
assert(trash->delete_nesting == 1);
2748-
}
2749-
--trash->delete_nesting;
2750-
}
2751-
2752-
2753-
static struct _py_trashcan *
2754-
_PyTrash_get_state(PyThreadState *tstate)
2755-
{
2756-
if (tstate != NULL) {
2757-
return &tstate->trash;
2758-
}
2759-
// The current thread must be finalizing.
2760-
// Fall back to using thread-local state.
2761-
// XXX Use thread-local variable syntax?
2762-
assert(PyThread_tss_is_created(&_PyRuntime.trashTSSkey));
2763-
struct _py_trashcan *trash =
2764-
(struct _py_trashcan *)PyThread_tss_get(&_PyRuntime.trashTSSkey);
2765-
if (trash == NULL) {
2766-
trash = PyMem_RawMalloc(sizeof(struct _py_trashcan));
2767-
if (trash == NULL) {
2768-
Py_FatalError("Out of memory");
2769-
}
2770-
PyThread_tss_set(&_PyRuntime.trashTSSkey, (void *)trash);
2771-
}
2772-
return trash;
2773-
}
2774-
2775-
static void
2776-
_PyTrash_clear_state(PyThreadState *tstate)
2777-
{
2778-
if (tstate != NULL) {
2779-
assert(tstate->trash.delete_later == NULL);
2780-
return;
2781-
}
2782-
if (PyThread_tss_is_created(&_PyRuntime.trashTSSkey)) {
2783-
struct _py_trashcan *trash =
2784-
(struct _py_trashcan *)PyThread_tss_get(&_PyRuntime.trashTSSkey);
2785-
if (trash != NULL) {
2786-
PyThread_tss_set(&_PyRuntime.trashTSSkey, (void *)NULL);
2787-
PyMem_RawFree(trash);
2788-
}
27892747
}
2748+
tstate->c_recursion_remaining++;
27902749
}
27912750

2792-
2793-
int
2794-
_PyTrash_begin(PyThreadState *tstate, PyObject *op)
2795-
{
2796-
// XXX Make sure the GIL is held.
2797-
struct _py_trashcan *trash = _PyTrash_get_state(tstate);
2798-
if (trash->delete_nesting >= _PyTrash_UNWIND_LEVEL) {
2799-
/* Store the object (to be deallocated later) and jump past
2800-
* Py_TRASHCAN_END, skipping the body of the deallocator */
2801-
_PyTrash_thread_deposit_object(trash, op);
2802-
return 1;
2803-
}
2804-
++trash->delete_nesting;
2805-
return 0;
2806-
}
2807-
2808-
2809-
void
2810-
_PyTrash_end(PyThreadState *tstate)
2811-
{
2812-
// XXX Make sure the GIL is held.
2813-
struct _py_trashcan *trash = _PyTrash_get_state(tstate);
2814-
--trash->delete_nesting;
2815-
if (trash->delete_nesting <= 0) {
2816-
if (trash->delete_later != NULL) {
2817-
_PyTrash_thread_destroy_chain(trash);
2818-
}
2819-
_PyTrash_clear_state(tstate);
2820-
}
2821-
}
2822-
2823-
2824-
/* bpo-40170: It's only be used in Py_TRASHCAN_BEGIN macro to hide
2825-
implementation details. */
2826-
int
2827-
_PyTrash_cond(PyObject *op, destructor dealloc)
2828-
{
2829-
return Py_TYPE(op)->tp_dealloc == dealloc;
2830-
}
2831-
2832-
28332751
void _Py_NO_RETURN
28342752
_PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
28352753
const char *file, int line, const char *function)

Python/pystate.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,8 @@ init_threadstate(_PyThreadStateImpl *_tstate,
14781478
tstate->what_event = -1;
14791479
tstate->previous_executor = NULL;
14801480

1481+
tstate->delete_later = NULL;
1482+
14811483
llist_init(&_tstate->mem_free_queue);
14821484

14831485
if (interp->stoptheworld.requested || _PyRuntime.stoptheworld.requested) {

0 commit comments

Comments
 (0)