mirror of https://github.com/python/cpython
GH-117760: Streamline the trashcan mechanism (GH-117763)
This commit is contained in:
parent
c917b3e8e1
commit
147cd0581e
|
@ -448,8 +448,8 @@ without deallocating anything (and so unbounded call-stack depth is avoided).
|
||||||
When the call stack finishes unwinding again, code generated by the END macro
|
When the call stack finishes unwinding again, code generated by the END macro
|
||||||
notices this, and calls another routine to deallocate all the objects that
|
notices this, and calls another routine to deallocate all the objects that
|
||||||
may have been added to the list of deferred deallocations. In effect, a
|
may have been added to the list of deferred deallocations. In effect, a
|
||||||
chain of N deallocations is broken into (N-1)/(_PyTrash_UNWIND_LEVEL-1) pieces,
|
chain of N deallocations is broken into (N-1)/(Py_TRASHCAN_HEADROOM-1) pieces,
|
||||||
with the call stack never exceeding a depth of _PyTrash_UNWIND_LEVEL.
|
with the call stack never exceeding a depth of Py_TRASHCAN_HEADROOM.
|
||||||
|
|
||||||
Since the tp_dealloc of a subclass typically calls the tp_dealloc of the base
|
Since the tp_dealloc of a subclass typically calls the tp_dealloc of the base
|
||||||
class, we need to ensure that the trashcan is only triggered on the tp_dealloc
|
class, we need to ensure that the trashcan is only triggered on the tp_dealloc
|
||||||
|
@ -461,30 +461,33 @@ passed as second argument to Py_TRASHCAN_BEGIN().
|
||||||
/* Python 3.9 private API, invoked by the macros below. */
|
/* Python 3.9 private API, invoked by the macros below. */
|
||||||
PyAPI_FUNC(int) _PyTrash_begin(PyThreadState *tstate, PyObject *op);
|
PyAPI_FUNC(int) _PyTrash_begin(PyThreadState *tstate, PyObject *op);
|
||||||
PyAPI_FUNC(void) _PyTrash_end(PyThreadState *tstate);
|
PyAPI_FUNC(void) _PyTrash_end(PyThreadState *tstate);
|
||||||
/* Python 3.10 private API, invoked by the Py_TRASHCAN_BEGIN(). */
|
|
||||||
PyAPI_FUNC(int) _PyTrash_cond(PyObject *op, destructor dealloc);
|
|
||||||
|
|
||||||
#define Py_TRASHCAN_BEGIN_CONDITION(op, cond) \
|
PyAPI_FUNC(void) _PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op);
|
||||||
do { \
|
PyAPI_FUNC(void) _PyTrash_thread_destroy_chain(PyThreadState *tstate);
|
||||||
PyThreadState *_tstate = NULL; \
|
|
||||||
/* If "cond" is false, then _tstate remains NULL and the deallocator \
|
|
||||||
* is run normally without involving the trashcan */ \
|
/* Python 3.10 private API, invoked by the Py_TRASHCAN_BEGIN(). */
|
||||||
if (cond) { \
|
|
||||||
_tstate = PyThreadState_GetUnchecked(); \
|
/* To avoid raising recursion errors during dealloc trigger trashcan before we reach
|
||||||
if (_PyTrash_begin(_tstate, _PyObject_CAST(op))) { \
|
* recursion limit. To avoid trashing, we don't attempt to empty the trashcan until
|
||||||
break; \
|
* we have headroom above the trigger limit */
|
||||||
} \
|
#define Py_TRASHCAN_HEADROOM 50
|
||||||
}
|
|
||||||
/* The body of the deallocator is here. */
|
|
||||||
#define Py_TRASHCAN_END \
|
|
||||||
if (_tstate) { \
|
|
||||||
_PyTrash_end(_tstate); \
|
|
||||||
} \
|
|
||||||
} while (0);
|
|
||||||
|
|
||||||
#define Py_TRASHCAN_BEGIN(op, dealloc) \
|
#define Py_TRASHCAN_BEGIN(op, dealloc) \
|
||||||
Py_TRASHCAN_BEGIN_CONDITION((op), \
|
do { \
|
||||||
_PyTrash_cond(_PyObject_CAST(op), (destructor)(dealloc)))
|
PyThreadState *tstate = PyThreadState_Get(); \
|
||||||
|
if (tstate->c_recursion_remaining <= Py_TRASHCAN_HEADROOM && Py_TYPE(op)->tp_dealloc == (destructor)dealloc) { \
|
||||||
|
_PyTrash_thread_deposit_object(tstate, (PyObject *)op); \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
tstate->c_recursion_remaining--;
|
||||||
|
/* The body of the deallocator is here. */
|
||||||
|
#define Py_TRASHCAN_END \
|
||||||
|
tstate->c_recursion_remaining++; \
|
||||||
|
if (tstate->delete_later && tstate->c_recursion_remaining > (Py_TRASHCAN_HEADROOM*2)) { \
|
||||||
|
_PyTrash_thread_destroy_chain(tstate); \
|
||||||
|
} \
|
||||||
|
} while (0);
|
||||||
|
|
||||||
|
|
||||||
PyAPI_FUNC(void *) PyObject_GetItemData(PyObject *obj);
|
PyAPI_FUNC(void *) PyObject_GetItemData(PyObject *obj);
|
||||||
|
|
|
@ -56,11 +56,6 @@ 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 */
|
||||||
|
|
||||||
|
@ -152,7 +147,7 @@ struct _ts {
|
||||||
*/
|
*/
|
||||||
unsigned long native_thread_id;
|
unsigned long native_thread_id;
|
||||||
|
|
||||||
struct _py_trashcan trash;
|
PyObject *delete_later;
|
||||||
|
|
||||||
/* Tagged pointer to top-most critical section, or zero if there is no
|
/* Tagged pointer to top-most critical section, or zero if there is no
|
||||||
* active critical section. Critical sections are only used in
|
* active critical section. Critical sections are only used in
|
||||||
|
|
|
@ -615,7 +615,6 @@ _PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(PyObject *op)
|
||||||
return (PyWeakReference **)((char *)op + offset);
|
return (PyWeakReference **)((char *)op + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Fast inlined version of PyObject_IS_GC()
|
// Fast inlined version of PyObject_IS_GC()
|
||||||
static inline int
|
static inline int
|
||||||
_PyObject_IS_GC(PyObject *obj)
|
_PyObject_IS_GC(PyObject *obj)
|
||||||
|
|
114
Objects/object.c
114
Objects/object.c
|
@ -2709,33 +2709,31 @@ finally:
|
||||||
|
|
||||||
/* Trashcan support. */
|
/* Trashcan support. */
|
||||||
|
|
||||||
#define _PyTrash_UNWIND_LEVEL 50
|
|
||||||
|
|
||||||
/* Add op to the gcstate->trash_delete_later list. Called when the current
|
/* Add op to the gcstate->trash_delete_later list. Called when the current
|
||||||
* call-stack depth gets large. op must be a currently untracked gc'ed
|
* call-stack depth gets large. op must be a currently untracked gc'ed
|
||||||
* 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
|
void
|
||||||
_PyTrash_thread_deposit_object(struct _py_trashcan *trash, PyObject *op)
|
_PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op)
|
||||||
{
|
{
|
||||||
_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);
|
||||||
#ifdef Py_GIL_DISABLED
|
#ifdef Py_GIL_DISABLED
|
||||||
_PyObject_ASSERT(op, op->ob_tid == 0);
|
_PyObject_ASSERT(op, op->ob_tid == 0);
|
||||||
op->ob_tid = (uintptr_t)trash->delete_later;
|
op->ob_tid = (uintptr_t)tstate->delete_later;
|
||||||
#else
|
#else
|
||||||
_PyGCHead_SET_PREV(_Py_AS_GC(op), (PyGC_Head*)trash->delete_later);
|
_PyGCHead_SET_PREV(_Py_AS_GC(op), (PyGC_Head*)tstate->delete_later);
|
||||||
#endif
|
#endif
|
||||||
trash->delete_later = op;
|
tstate->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
|
void
|
||||||
_PyTrash_thread_destroy_chain(struct _py_trashcan *trash)
|
_PyTrash_thread_destroy_chain(PyThreadState *tstate)
|
||||||
{
|
{
|
||||||
/* We need to increase trash_delete_nesting here, otherwise,
|
/* We need to increase c_recursion_remaining 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
|
||||||
increase:
|
increase:
|
||||||
|
@ -2746,17 +2744,17 @@ _PyTrash_thread_destroy_chain(struct _py_trashcan *trash)
|
||||||
tups = [(tup,) for tup in tups]
|
tups = [(tup,) for tup in tups]
|
||||||
del tups
|
del tups
|
||||||
*/
|
*/
|
||||||
assert(trash->delete_nesting == 0);
|
assert(tstate->c_recursion_remaining > Py_TRASHCAN_HEADROOM);
|
||||||
++trash->delete_nesting;
|
tstate->c_recursion_remaining--;
|
||||||
while (trash->delete_later) {
|
while (tstate->delete_later) {
|
||||||
PyObject *op = trash->delete_later;
|
PyObject *op = tstate->delete_later;
|
||||||
destructor dealloc = Py_TYPE(op)->tp_dealloc;
|
destructor dealloc = Py_TYPE(op)->tp_dealloc;
|
||||||
|
|
||||||
#ifdef Py_GIL_DISABLED
|
#ifdef Py_GIL_DISABLED
|
||||||
trash->delete_later = (PyObject*) op->ob_tid;
|
tstate->delete_later = (PyObject*) op->ob_tid;
|
||||||
op->ob_tid = 0;
|
op->ob_tid = 0;
|
||||||
#else
|
#else
|
||||||
trash->delete_later = (PyObject*) _PyGCHead_PREV(_Py_AS_GC(op));
|
tstate->delete_later = (PyObject*) _PyGCHead_PREV(_Py_AS_GC(op));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Call the deallocator directly. This used to try to
|
/* Call the deallocator directly. This used to try to
|
||||||
|
@ -2767,92 +2765,10 @@ _PyTrash_thread_destroy_chain(struct _py_trashcan *trash)
|
||||||
*/
|
*/
|
||||||
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
|
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
|
||||||
(*dealloc)(op);
|
(*dealloc)(op);
|
||||||
assert(trash->delete_nesting == 1);
|
|
||||||
}
|
}
|
||||||
--trash->delete_nesting;
|
tstate->c_recursion_remaining++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int
|
|
||||||
_PyTrash_begin(PyThreadState *tstate, PyObject *op)
|
|
||||||
{
|
|
||||||
// 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
|
|
||||||
* Py_TRASHCAN_END, skipping the body of the deallocator */
|
|
||||||
_PyTrash_thread_deposit_object(trash, op);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
++trash->delete_nesting;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
_PyTrash_end(PyThreadState *tstate)
|
|
||||||
{
|
|
||||||
// XXX Make sure the GIL is held.
|
|
||||||
struct _py_trashcan *trash = _PyTrash_get_state(tstate);
|
|
||||||
--trash->delete_nesting;
|
|
||||||
if (trash->delete_nesting <= 0) {
|
|
||||||
if (trash->delete_later != NULL) {
|
|
||||||
_PyTrash_thread_destroy_chain(trash);
|
|
||||||
}
|
|
||||||
_PyTrash_clear_state(tstate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* bpo-40170: It's only be used in Py_TRASHCAN_BEGIN macro to hide
|
|
||||||
implementation details. */
|
|
||||||
int
|
|
||||||
_PyTrash_cond(PyObject *op, destructor dealloc)
|
|
||||||
{
|
|
||||||
return Py_TYPE(op)->tp_dealloc == dealloc;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void _Py_NO_RETURN
|
void _Py_NO_RETURN
|
||||||
_PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
|
_PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
|
||||||
const char *file, int line, const char *function)
|
const char *file, int line, const char *function)
|
||||||
|
|
|
@ -1485,6 +1485,8 @@ init_threadstate(_PyThreadStateImpl *_tstate,
|
||||||
tstate->what_event = -1;
|
tstate->what_event = -1;
|
||||||
tstate->previous_executor = NULL;
|
tstate->previous_executor = NULL;
|
||||||
|
|
||||||
|
tstate->delete_later = NULL;
|
||||||
|
|
||||||
llist_init(&_tstate->mem_free_queue);
|
llist_init(&_tstate->mem_free_queue);
|
||||||
|
|
||||||
if (interp->stoptheworld.requested || _PyRuntime.stoptheworld.requested) {
|
if (interp->stoptheworld.requested || _PyRuntime.stoptheworld.requested) {
|
||||||
|
|
Loading…
Reference in New Issue