Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5ff2fbc

Browse files
authored
gh-129236: Use stackpointer in free threaded GC (#129240)
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 a292216 commit 5ff2fbc

File tree

2 files changed

+52
-52
lines changed

2 files changed

+52
-52
lines changed

Include/internal/pycore_frame.h

Lines changed: 0 additions & 24 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
@@ -222,16 +215,6 @@ _PyFrame_Initialize(
222215
for (int i = null_locals_from; i < code->co_nlocalsplus; i++) {
223216
frame->localsplus[i] = PyStackRef_NULL;
224217
}
225-
226-
#ifdef Py_GIL_DISABLED
227-
// On GIL disabled, we walk the entire stack in GC. Since stacktop
228-
// is not always in sync with the real stack pointer, we have
229-
// no choice but to traverse the entire stack.
230-
// This just makes sure we don't pass the GC invalid stack values.
231-
for (int i = code->co_nlocalsplus; i < code->co_nlocalsplus + code->co_stacksize; i++) {
232-
frame->localsplus[i] = PyStackRef_NULL;
233-
}
234-
#endif
235218
}
236219

237220
/* Gets the pointer to the locals array
@@ -405,13 +388,6 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
405388
frame->lltrace = 0;
406389
#endif
407390
frame->return_offset = 0;
408-
409-
#ifdef Py_GIL_DISABLED
410-
assert(code->co_nlocalsplus == 0);
411-
for (int i = 0; i < code->co_stacksize; i++) {
412-
frame->localsplus[i] = PyStackRef_NULL;
413-
}
414-
#endif
415391
return frame;
416392
}
417393

Python/gc_free_threading.c

Lines changed: 52 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 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,27 @@ 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 in cases where
441+
// the GC is run during a PyStackRef_CLOSE() call. Skip this
442+
// frame and don't collect objects with deferred references.
443+
state->skip_deferred_objects = 1;
435444
continue;
436445
}
437446

438-
PyCodeObject *co = (PyCodeObject *)executable;
439-
int max_stack = co->co_nlocalsplus + co->co_stacksize;
440447
gc_visit_stackref(f->f_executable);
441-
for (int i = 0; i < max_stack; i++) {
442-
gc_visit_stackref(f->localsplus[i]);
448+
while (top != f->localsplus) {
449+
--top;
450+
gc_visit_stackref(*top);
443451
}
444452
}
445453
}
@@ -519,10 +527,7 @@ gc_abort_mark_alive(PyInterpreterState *interp,
519527
static int
520528
gc_visit_stackref_mark_alive(_PyObjectStack *stack, _PyStackRef stackref)
521529
{
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)) {
530+
if (!PyStackRef_IsNull(stackref)) {
526531
PyObject *op = PyStackRef_AsPyObjectBorrow(stackref);
527532
if (mark_alive_stack_push(op, stack) < 0) {
528533
return -1;
@@ -534,27 +539,37 @@ gc_visit_stackref_mark_alive(_PyObjectStack *stack, _PyStackRef stackref)
534539
static int
535540
gc_visit_thread_stacks_mark_alive(PyInterpreterState *interp, _PyObjectStack *stack)
536541
{
542+
int err = 0;
537543
_Py_FOR_EACH_TSTATE_BEGIN(interp, p) {
538544
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)) {
545+
if (f->owner >= FRAME_OWNED_BY_INTERPRETER) {
541546
continue;
542547
}
543548

544-
PyCodeObject *co = (PyCodeObject *)executable;
545-
int max_stack = co->co_nlocalsplus + co->co_stacksize;
549+
if (f->stackpointer == NULL) {
550+
// GH-129236: The stackpointer may be NULL in cases where
551+
// the GC is run during a PyStackRef_CLOSE() call. Skip this
552+
// frame for now.
553+
continue;
554+
}
555+
556+
_PyStackRef *top = f->stackpointer;
546557
if (gc_visit_stackref_mark_alive(stack, f->f_executable) < 0) {
547-
return -1;
558+
err = -1;
559+
goto exit;
548560
}
549-
for (int i = 0; i < max_stack; i++) {
550-
if (gc_visit_stackref_mark_alive(stack, f->localsplus[i]) < 0) {
551-
return -1;
561+
while (top != f->localsplus) {
562+
--top;
563+
if (gc_visit_stackref_mark_alive(stack, *top) < 0) {
564+
err = -1;
565+
goto exit;
552566
}
553567
}
554568
}
555569
}
570+
exit:
556571
_Py_FOR_EACH_TSTATE_END(interp);
557-
return 0;
572+
return err;
558573
}
559574
#endif // GC_MARK_ALIVE_STACKS
560575
#endif // GC_ENABLE_MARK_ALIVE
@@ -789,14 +804,23 @@ mark_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area,
789804
return true;
790805
}
791806

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

796-
_PyObject_ASSERT_WITH_MSG(op, gc_get_refs(op) >= 0,
797-
"refcount is too small");
815+
// GH-129236: If we've seen an active frame without a valid stack pointer,
816+
// then we can't collect objects with deferred references because we may
817+
// have missed some reference to the object on the stack. In that case,
818+
// treat the object as reachable even if gc_refs is zero.
819+
struct collection_state *state = (struct collection_state *)args;
820+
int keep_alive = (state->skip_deferred_objects &&
821+
_PyObject_HasDeferredRefcount(op));
798822

799-
if (gc_is_unreachable(op) && gc_get_refs(op) != 0) {
823+
if (gc_get_refs(op) != 0 || keep_alive) {
800824
// Object is reachable but currently marked as unreachable.
801825
// Mark it as reachable and traverse its pointers to find
802826
// any other object that may be directly reachable from it.
@@ -985,7 +1009,7 @@ deduce_unreachable_heap(PyInterpreterState *interp,
9851009
#endif
9861010

9871011
// Visit the thread stacks to account for any deferred references.
988-
gc_visit_thread_stacks(interp);
1012+
gc_visit_thread_stacks(interp, state);
9891013

9901014
// Transitively mark reachable objects by clearing the
9911015
// _PyGC_BITS_UNREACHABLE flag.

0 commit comments

Comments
 (0)