Skip to content

Subclass of extension type with reference cycle sometimes never gets GC'd on free-threaded build #125853

Closed
@ngoldbaum

Description

@ngoldbaum

Bug report

Bug description:

ping @colesbury

Apply the following diff on top of the current PyO3 main branch:

diff --git a/tests/test_gc.rs b/tests/test_gc.rs
index 9483819c..13a617a5 100644
--- a/tests/test_gc.rs
+++ b/tests/test_gc.rs
@@ -627,12 +627,9 @@ fn test_traverse_subclass() {
             check.assert_not_dropped();
         }
 
-        #[cfg(not(Py_GIL_DISABLED))]
-        {
-            // FIXME: seems like a bug that this is flaky on the free-threaded build
-            // https://github.com/PyO3/pyo3/issues/4627
-            check.assert_drops_with_gc(ptr);
-        }
+        // FIXME: seems like a bug that this is flaky on the free-threaded build
+        // https://github.com/PyO3/pyo3/issues/4627
+        check.assert_drops_with_gc(ptr);
 
         #[cfg(Py_GIL_DISABLED)]
         {
@@ -685,12 +682,9 @@ fn test_traverse_subclass_override_clear() {
             check.assert_not_dropped();
         }
 
-        #[cfg(not(Py_GIL_DISABLED))]
-        {
-            // FIXME: seems like a bug that this is flaky on the free-threaded build
-            // https://github.com/PyO3/pyo3/issues/4627
-            check.assert_drops_with_gc(ptr);
-        }
+        // FIXME: seems like a bug that this is flaky on the free-threaded build
+        // https://github.com/PyO3/pyo3/issues/4627
+        check.assert_drops_with_gc(ptr);
 
         #[cfg(Py_GIL_DISABLED)]
         {

Then do:

$ while UNSAFE_PYO3_BUILD_FREE_THREADED=1 cargo test --test test_gc; do :; done

Eventually, one or both tests modified by the diff above will fail:

test test_traverse_subclass ... FAILED
test test_traverse_subclass_override_clear ... FAILED

failures:

---- test_traverse_subclass stdout ----
thread 'test_traverse_subclass' panicked at tests/test_gc.rs:632:15:
Object was not dropped after 100 GC cycles, refcount is 2

---- test_traverse_subclass_override_clear stdout ----
thread 'test_traverse_subclass_override_clear' panicked at tests/test_gc.rs:687:15:
Object was not dropped after 100 GC cycles, refcount is 2


failures:
    test_traverse_subclass
    test_traverse_subclass_override_clear

assert_drops_with_gc does 100 loops of calling gc.collect() and then sleeping for five milliseconds. There are other tests that make use of the same infrastructure and they always complete quickly on my machine without getting anywhere close to 100 iterations. You can also increase the hard-coded 100 in the for loop to an arbitrarily large number and the object never gets GC'd in my testing, even after waiting for longer than a minute.

See PyO3/pyo3#4627 for the PyO3 issue, which has some additional details.

CPython versions tested on:

3.13

Operating systems tested on:

macOS

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions