Skip to content

Commit 2d81b6b

Browse files
committed
Restrict trashcan to GC'ed objects and thread the trashcan list through the GC header
1 parent 343719d commit 2d81b6b

2 files changed

Lines changed: 31 additions & 59 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Restrict the trashcan mechanism to GC'ed objects and untrack them while in
2+
the trashcan to prevent the GC and trashcan mechanisms conflicting.

Objects/object.c

Lines changed: 29 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3034,57 +3034,24 @@ Py_ReprLeave(PyObject *obj)
30343034

30353035
/* Trashcan support. */
30363036

3037-
#ifndef Py_GIL_DISABLED
3038-
/* We need to store a pointer in the refcount field of
3039-
* an object. It is important that we never store 0 (NULL).
3040-
* It is also important to not make the object appear immortal,
3041-
* or it might be untracked by the cycle GC. */
3042-
static uintptr_t
3043-
pointer_to_safe_refcount(void *ptr)
3044-
{
3045-
uintptr_t full = (uintptr_t)ptr;
3046-
assert((full & 3) == 0);
3047-
#if SIZEOF_VOID_P > 4
3048-
uint32_t refcnt = (uint32_t)full;
3049-
if (refcnt >= (uint32_t)_Py_IMMORTAL_MINIMUM_REFCNT) {
3050-
full = full - ((uintptr_t)_Py_IMMORTAL_MINIMUM_REFCNT) + 1;
3051-
}
3052-
return full + 2;
3053-
#else
3054-
// Make the top two bits 0, so it appears mortal.
3055-
return (full >> 2) + 1;
3056-
#endif
3057-
}
3058-
3059-
static void *
3060-
safe_refcount_to_pointer(uintptr_t refcnt)
3061-
{
3062-
#if SIZEOF_VOID_P > 4
3063-
if (refcnt & 1) {
3064-
refcnt += _Py_IMMORTAL_MINIMUM_REFCNT - 1;
3065-
}
3066-
return (void *)(refcnt - 2);
3067-
#else
3068-
return (void *)((refcnt -1) << 2);
3069-
#endif
3070-
}
3071-
#endif
3072-
30733037
/* Add op to the gcstate->trash_delete_later list. Called when the current
3074-
* call-stack depth gets large. op must be a currently untracked gc'ed
3075-
* object, with refcount 0. Py_DECREF must already have been called on it.
3038+
* call-stack depth gets large. op must be a gc'ed object, with refcount 0.
3039+
* Py_DECREF must already have been called on it.
30763040
*/
30773041
void
30783042
_PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op)
30793043
{
30803044
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
3045+
assert(PyObject_IS_GC(op));
3046+
int tracked = _PyObject_GC_IS_TRACKED(op);
3047+
if (tracked) {
3048+
_PyObject_GC_UNTRACK(op);
3049+
}
3050+
uintptr_t tagged_ptr = ((uintptr_t)tstate->delete_later) | tracked;
30813051
#ifdef Py_GIL_DISABLED
3082-
op->ob_tid = (uintptr_t)tstate->delete_later;
3052+
op->ob_tid = tagged_ptr;
30833053
#else
3084-
/* Store the delete_later pointer in the refcnt field. */
3085-
uintptr_t refcnt = pointer_to_safe_refcount(tstate->delete_later);
3086-
*((uintptr_t*)op) = refcnt;
3087-
assert(!_Py_IsImmortal(op));
3054+
_Py_AS_GC(op)->_gc_next = tagged_ptr;
30883055
#endif
30893056
tstate->delete_later = op;
30903057
}
@@ -3099,25 +3066,28 @@ _PyTrash_thread_destroy_chain(PyThreadState *tstate)
30993066
destructor dealloc = Py_TYPE(op)->tp_dealloc;
31003067

31013068
#ifdef Py_GIL_DISABLED
3102-
tstate->delete_later = (PyObject*) op->ob_tid;
3069+
uintptr_t tagged_ptr = op->ob_tid;
31033070
op->ob_tid = 0;
31043071
_Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_MERGED);
31053072
#else
3106-
/* Get the delete_later pointer from the refcnt field.
3107-
* See _PyTrash_thread_deposit_object(). */
3108-
uintptr_t refcnt = *((uintptr_t*)op);
3109-
tstate->delete_later = safe_refcount_to_pointer(refcnt);
3110-
op->ob_refcnt = 0;
3073+
uintptr_t tagged_ptr = _Py_AS_GC(op)->_gc_next;
3074+
_Py_AS_GC(op)->_gc_next = 0;
31113075
#endif
3112-
3113-
/* Call the deallocator directly. This used to try to
3114-
* fool Py_DECREF into calling it indirectly, but
3115-
* Py_DECREF was already called on this object, and in
3116-
* assorted non-release builds calling Py_DECREF again ends
3117-
* up distorting allocation statistics.
3118-
*/
3119-
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
3120-
(*dealloc)(op);
3076+
tstate->delete_later = (PyObject *)(tagged_ptr & ~1);
3077+
if (tagged_ptr & 1) {
3078+
_PyObject_GC_TRACK(op);
3079+
}
3080+
/* It is possible that the object has been accessed through
3081+
* a weak ref, so only free if refcount == 0) */
3082+
if (Py_REFCNT(op) == 0) {
3083+
/* Call the deallocator directly. This used to try to
3084+
* fool Py_DECREF into calling it indirectly, but
3085+
* Py_DECREF was already called on this object, and in
3086+
* assorted non-release builds calling Py_DECREF again ends
3087+
* up distorting allocation statistics.
3088+
*/
3089+
(*dealloc)(op);
3090+
}
31213091
}
31223092
}
31233093

@@ -3186,7 +3156,7 @@ _Py_Dealloc(PyObject *op)
31863156
destructor dealloc = type->tp_dealloc;
31873157
PyThreadState *tstate = _PyThreadState_GET();
31883158
intptr_t margin = _Py_RecursionLimit_GetMargin(tstate);
3189-
if (margin < 2) {
3159+
if (margin < 2 && PyObject_IS_GC(op)) {
31903160
_PyTrash_thread_deposit_object(tstate, (PyObject *)op);
31913161
return;
31923162
}

0 commit comments

Comments
 (0)