Description
Crash report
What happened?
Playing with the code from #130148, I stumbled on a segfault in a free-threaded debug build with the following (seems to trigger faster/more consistently when pasted in the REPL):
from contextlib import redirect_stdout
from io import StringIO
from threading import Thread
import time
def test_redirect():
text = StringIO()
with redirect_stdout(text):
print("hello1", file=text)
time.sleep(0.1)
print("hello2", file=text)
print(text.getvalue())
assert text.getvalue() == "hello1\nhello2\n"
for x in range(100):
Thread(target=test_redirect, args=()).start()
I didn't have time to check whether JIT, debug or no-gil are strictly necessary for this to crash. JIT is not needed for this to crash.
Cause
The problem is that in the print()
implementation, _PySys_GetAttr
returns a borrowed reference.
Lines 2173 to 2175 in 655fc8a
So if sys.stdout
changes concurrently with the print()
, the program may crash because file
will point to a deallocated Python object.
This affects the GIL-enabled build as well. See this reproducer: https://gist.github.com/colesbury/c48f50e95d5d68e24814a56e2664e587
Suggested fix
Introduce a _PySys_GetAttrRef
that returns a new reference instead of a borrowed reference. Use that instead.
We should audit all the uses of _PySys_GetAttr
and PySys_GetObject
. I expect most of them will need to be replaced with functions that return new references, but it doesn't all have to be in a single PR.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.14.0a5+ experimental free-threading build (heads/main:359c7dde3bb, Feb 15 2025, 17:54:11) [GCC 11.4.0]