diff --git a/Lib/test/test_free_threading/test_gc.py b/Lib/test/test_free_threading/test_gc.py index 30282a111345f86..b287b51826999c7 100644 --- a/Lib/test/test_free_threading/test_gc.py +++ b/Lib/test/test_free_threading/test_gc.py @@ -97,6 +97,19 @@ def evil(): thread.start() thread.join() + def test_running_method_not_collected(self): + class C: + def m(self): + wr = weakref.ref(C.__dict__["m"]) + del C.m + gc.collect() + return wr() is not None + + def run(self): + return self.m() + + self.assertTrue(C().run()) + def test_merge_brc_queue_of_detached_thread(self): # GH-157838: objects queued for merging by a thread that is detached # (blocked in a lock acquire, sleep, etc.) are merged and freed on its diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-26-12-00-00.gh-issue-160682.Q7mK2x.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-26-12-00-00.gh-issue-160682.Q7mK2x.rst new file mode 100644 index 000000000000000..5c783665e789659 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-26-12-00-00.gh-issue-160682.Q7mK2x.rst @@ -0,0 +1,2 @@ +Fix a bug in the :term:`free-threaded build` where garbage collection could +prematurely collect the function object of a running method. diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 6db010468456eaa..6eaeeea8848cdbf 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -470,6 +470,7 @@ gc_visit_thread_stacks(PyInterpreterState *interp, struct collection_state *stat continue; } + gc_visit_stackref(f->f_funcobj); gc_visit_stackref(f->f_executable); while (top != f->localsplus) { --top; @@ -866,6 +867,10 @@ gc_visit_thread_stacks_mark_alive(PyInterpreterState *interp, gc_mark_args_t *ar } _PyStackRef *top = f->stackpointer; + if (gc_visit_stackref_mark_alive(args, f->f_funcobj) < 0) { + err = -1; + goto exit; + } if (gc_visit_stackref_mark_alive(args, f->f_executable) < 0) { err = -1; goto exit;