Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use of gc.get_referrers() in register_random breaks in free-threaded CPython 3.13 #3965

Closed
ngoldbaum opened this issue May 3, 2024 · 1 comment · Fixed by #3976
Closed
Labels
interop how to play nicely with other packages legibility make errors helpful and Hypothesis grokable

Comments

@ngoldbaum
Copy link
Contributor

See python/cpython#118522 for where I initially thought this was a CPython bug.

In the Python 3.13 free-threaded build, the current plan is for certain objects to be marked as immortal if any threads are created at runtime. See python/cpython#117783. In Python 3.14 these objects will be reference counted using a deferred reference counting strategy.

This change in Python 3.13 breaks the check in register_random based on gc.get_referrers(). You can reproduce this by first setting up a free-threaded build of python 3.13 (see https://github.com/quansight-labs/free-threaded-compatibility for more details on setting up a python 3.13 free-threaded dev environment) and then applying the following patch to a checkout of the numpy repo:

diff --git a/numpy/_core/tests/test_arrayprint.py b/numpy/_core/tests/test_arrayprint.py
index d9caced3c1..c70d68c9d7 100644
--- a/numpy/_core/tests/test_arrayprint.py
+++ b/numpy/_core/tests/test_arrayprint.py
@@ -1,6 +1,7 @@
 import sys
+import threading
 import gc
-from hypothesis import given
+from hypothesis import given, reproduce_failure
 from hypothesis.extra import numpy as hynp
 import pytest
 
@@ -12,6 +13,13 @@
 from numpy._core.arrayprint import _typelessdata
 import textwrap
 
+def dummy():
+    return
+
+t = threading.Thread(target=dummy)
+t.start()
+t.join()
+
 class TestArrayRepr:
     def test_nan_inf(self):
         x = np.array([np.nan, np.inf])
@@ -500,6 +508,7 @@ def test_nested_array_repr(self):
             '                     [1.]])]], dtype=object)'
         )
 
+    @reproduce_failure('6.100.2', b'AA==')
     @given(hynp.from_dtype(np.dtype("U")))
     def test_any_text(self, text):
         # This test checks that, given any value that can be represented in an

You can then execute the test with

spin test -- "numpy/_core/tests/test_arrayprint.py::TestArray2String::test_any_text"

And you'll see the warning about an RNG possibly getting garbage collected spuriously raised for hypothesis' own default global RNG instance:

>                   warnings.warn(
                        "It looks like `register_random` was passed an object that could "
                        "be garbage collected immediately after `register_random` creates "
                        "a weakref to it. This will prevent Hypothesis from managing this "
                        "PRNG. See the docs for `register_random` for more details.",
                        HypothesisWarning,
                        stacklevel=2,
                    )
E                   hypothesis.errors.HypothesisWarning: It looks like `register_random` was passed an object that could be garbage collected immediately after `register_random` creates a weakref to it. This will prevent Hypothesis from managing this PRNG. See the docs for `register_random` for more details.

r          = <random.Random object at 0x415d8f81e10>

/Users/goldbaum/.pyenv/versions/3.13-dev-bisect/lib/python3.13/site-packages/hypothesis/internal/entropy.py:131: HypothesisWarning
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB post_mortem (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /Users/goldbaum/.pyenv/versions/3.13-dev-bisect/lib/python3.13/site-packages/hypothesis/internal/entropy.py(131)register_random()
-> warnings.warn(
(Pdb) up
> /Users/goldbaum/.pyenv/versions/3.13-dev-bisect/lib/python3.13/site-packages/hypothesis/internal/entropy.py(192)deterministic_PRNG()
-> register_random(hypothesis.core._hypothesis_global_random)

I think maybe the easiest fix is to add an additional check like the one that's already there for pypy for the free-threaded python build. Note that this behavior will only happen if a thread is spawned before the RNG is registered, so the behavior of this function will be impacted by whether or not unrelated code spawned a thread.

@Zac-HD Zac-HD added legibility make errors helpful and Hypothesis grokable interop how to play nicely with other packages labels May 3, 2024
@Zac-HD
Copy link
Member

Zac-HD commented May 3, 2024

Wow, that's a tricky interaction - thanks for tracking it down and writing up such a great report 😍

I agree that disabling the check on affected builds seems like the best solution here; it'll still be useful to most people most of the time but without this tricky false alarm.

Would you be interested in opening a PR to add that extra condition?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interop how to play nicely with other packages legibility make errors helpful and Hypothesis grokable
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants