Skip to content

Commit 1ec055b

Browse files
committed
gh-129236: Use stackpointer in free threaded GC
The stack pointers in interpreter frames are nearly always valid now, so use them when visiting each thread's frame. For now, don't collect objects with deferred references in the rare case that we see a frame with a NULL stack pointer.
1 parent c05a851 commit 1ec055b

File tree

2 files changed

+50
-35
lines changed

2 files changed

+50
-35
lines changed

Include/internal/pycore_frame.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,6 @@ static inline void _PyFrame_Copy(_PyInterpreterFrame *src, _PyInterpreterFrame *
159159
// Don't leave a dangling pointer to the old frame when creating generators
160160
// and coroutines:
161161
dest->previous = NULL;
162-
163-
#ifdef Py_GIL_DISABLED
164-
PyCodeObject *co = _PyFrame_GetCode(dest);
165-
for (int i = stacktop; i < co->co_nlocalsplus + co->co_stacksize; i++) {
166-
dest->localsplus[i] = PyStackRef_NULL;
167-
}
168-
#endif
169162
}
170163

171164
#ifdef Py_GIL_DISABLED

Python/gc_free_threading.c

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ struct collection_state {
6767
PyInterpreterState *interp;
6868
GCState *gcstate;
6969
_PyGC_Reason reason;
70+
// GH-129236: If we see a an active frame without a valid stack pointer,
71+
// we can't collect objects with deferred references because we may not
72+
// see all references.
73+
int skip_deferred_objects;
7074
Py_ssize_t collected;
7175
Py_ssize_t uncollectable;
7276
Py_ssize_t long_lived_total;
@@ -413,9 +417,6 @@ gc_visit_heaps(PyInterpreterState *interp, mi_block_visit_fun *visitor,
413417
static inline void
414418
gc_visit_stackref(_PyStackRef stackref)
415419
{
416-
// Note: we MUST check that it is deferred before checking the rest.
417-
// Otherwise we might read into invalid memory due to non-deferred references
418-
// being dead already.
419420
if (PyStackRef_IsDeferred(stackref) && !PyStackRef_IsNull(stackref)) {
420421
PyObject *obj = PyStackRef_AsPyObjectBorrow(stackref);
421422
if (_PyObject_GC_IS_TRACKED(obj) && !gc_is_frozen(obj)) {
@@ -426,20 +427,26 @@ gc_visit_stackref(_PyStackRef stackref)
426427

427428
// Add 1 to the gc_refs for every deferred reference on each thread's stack.
428429
static void
429-
gc_visit_thread_stacks(PyInterpreterState *interp)
430+
gc_visit_thread_stacks(PyInterpreterState *interp, struct collection_state *state)
430431
{
431432
_Py_FOR_EACH_TSTATE_BEGIN(interp, p) {
432433
for (_PyInterpreterFrame *f = p->current_frame; f != NULL; f = f->previous) {
433-
PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable);
434-
if (executable == NULL || !PyCode_Check(executable)) {
434+
if (f->owner >= FRAME_OWNED_BY_INTERPRETER) {
435+
continue;
436+
}
437+
438+
_PyStackRef *top = f->stackpointer;
439+
if (top == NULL) {
440+
// GH-129236: The stackpointer may be NULL. Skip this frame
441+
// and don't collect objects with deferred references.
442+
state->skip_deferred_objects = 1;
435443
continue;
436444
}
437445

438-
PyCodeObject *co = (PyCodeObject *)executable;
439-
int max_stack = co->co_nlocalsplus + co->co_stacksize;
440446
gc_visit_stackref(f->f_executable);
441-
for (int i = 0; i < max_stack; i++) {
442-
gc_visit_stackref(f->localsplus[i]);
447+
while (top != f->localsplus) {
448+
--top;
449+
gc_visit_stackref(*top);
443450
}
444451
}
445452
}
@@ -519,10 +526,7 @@ gc_abort_mark_alive(PyInterpreterState *interp,
519526
static int
520527
gc_visit_stackref_mark_alive(_PyObjectStack *stack, _PyStackRef stackref)
521528
{
522-
// Note: we MUST check that it is deferred before checking the rest.
523-
// Otherwise we might read into invalid memory due to non-deferred references
524-
// being dead already.
525-
if (PyStackRef_IsDeferred(stackref) && !PyStackRef_IsNull(stackref)) {
529+
if (!PyStackRef_IsNull(stackref)) {
526530
PyObject *op = PyStackRef_AsPyObjectBorrow(stackref);
527531
if (mark_alive_stack_push(op, stack) < 0) {
528532
return -1;
@@ -534,27 +538,37 @@ gc_visit_stackref_mark_alive(_PyObjectStack *stack, _PyStackRef stackref)
534538
static int
535539
gc_visit_thread_stacks_mark_alive(PyInterpreterState *interp, _PyObjectStack *stack)
536540
{
541+
int err = 0;
537542
_Py_FOR_EACH_TSTATE_BEGIN(interp, p) {
538543
for (_PyInterpreterFrame *f = p->current_frame; f != NULL; f = f->previous) {
539-
PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable);
540-
if (executable == NULL || !PyCode_Check(executable)) {
544+
if (f->owner >= FRAME_OWNED_BY_INTERPRETER) {
541545
continue;
542546
}
543547

544-
PyCodeObject *co = (PyCodeObject *)executable;
545-
int max_stack = co->co_nlocalsplus + co->co_stacksize;
548+
if (f->stackpointer == NULL) {
549+
// GH-129236: The stackpointer may be NULL in cases where
550+
// the GC is run during a PyStackRef_CLOSE() call. Skip this
551+
// frame for now.
552+
continue;
553+
}
554+
555+
_PyStackRef *top = f->stackpointer;
546556
if (gc_visit_stackref_mark_alive(stack, f->f_executable) < 0) {
547-
return -1;
557+
err = -1;
558+
goto exit;
548559
}
549-
for (int i = 0; i < max_stack; i++) {
550-
if (gc_visit_stackref_mark_alive(stack, f->localsplus[i]) < 0) {
551-
return -1;
560+
while (top != f->localsplus) {
561+
--top;
562+
if (gc_visit_stackref_mark_alive(stack, *top) < 0) {
563+
err = -1;
564+
goto exit;
552565
}
553566
}
554567
}
555568
}
569+
exit:
556570
_Py_FOR_EACH_TSTATE_END(interp);
557-
return 0;
571+
return err;
558572
}
559573
#endif // GC_MARK_ALIVE_STACKS
560574
#endif // GC_ENABLE_MARK_ALIVE
@@ -789,14 +803,22 @@ mark_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area,
789803
return true;
790804
}
791805

792-
if (gc_is_alive(op)) {
806+
_PyObject_ASSERT_WITH_MSG(op, gc_get_refs(op) >= 0,
807+
"refcount is too small");
808+
809+
if (gc_is_alive(op) || !gc_is_unreachable(op)) {
810+
// Object was already marked as reachable.
793811
return true;
794812
}
795813

796-
_PyObject_ASSERT_WITH_MSG(op, gc_get_refs(op) >= 0,
797-
"refcount is too small");
814+
// GH-129236: Hack to avoid collect objects with deferred references
815+
// if we see a frame without a valid stack pointer.
816+
struct collection_state *state = (struct collection_state *)args;
817+
if (state->skip_deferred_objects && _PyObject_HasDeferredRefcount(op)) {
818+
gc_add_refs(op, 1);
819+
}
798820

799-
if (gc_is_unreachable(op) && gc_get_refs(op) != 0) {
821+
if (gc_get_refs(op) != 0) {
800822
// Object is reachable but currently marked as unreachable.
801823
// Mark it as reachable and traverse its pointers to find
802824
// any other object that may be directly reachable from it.
@@ -985,7 +1007,7 @@ deduce_unreachable_heap(PyInterpreterState *interp,
9851007
#endif
9861008

9871009
// Visit the thread stacks to account for any deferred references.
988-
gc_visit_thread_stacks(interp);
1010+
gc_visit_thread_stacks(interp, state);
9891011

9901012
// Transitively mark reachable objects by clearing the
9911013
// _PyGC_BITS_UNREACHABLE flag.

0 commit comments

Comments
 (0)