You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
contextlib.redirect_stdout/stderr does not work with pytest-run-parallel #130148
The underlying problem is probably the global sys.stdout/stderr state which is shared between threads. Is it possible to make these thread local?
I'm not sure we can do it. AFAIK, those standard streams are at the interpreter state level. I also don't know why we only have a single write and not more. It's as if one thread is not doing anything or something was not caught at all (I would not be surprised if for instance we had more "hello" but I'm surprised we only have one)
The reason for the one hello is that the second thread changes sys.stdout and this thread has more than two hellos. But I get only the first assertion with one hello.
changed the title [-]contextlib.redirect_stdout/stderr does not work with freethreading[/-][+]contextlib.redirect_stdout/stderr does not work with `pytest-run-parallel`[/+]on Feb 15, 2025
I don't think so, at least without making it a huge breaking change. If we were to make sys.stdout/sys.stderr thread-local, we'd break all programs that currently rely on that for catching data from all threads. In theory, I guess we could have some sort of opt-in system to make stdout and stderr thread-local using some dirty hacks, but it would be a lot of work and I'm not convinced it's worth it. I'd just suggest synchronizing your tests that use redirect_stdout with a lock.
I don't think so, at least without making it a huge breaking change.
That is what I expected, but I wanted to ask.
I'd just suggest synchronizing your tests that use redirect_stdout with a lock.
I would have to synchronize all Tests which are using sys.stdout. Every test which is using print could use the wrong output.
I see no way how redirect_stdout can be used in a multi threaded environment.
FWIW, redirect_stdout was never expected to work across threads, even in a build with a GIL.
Stdout being global state is an inconvenient fact-of-life (much like locale or the various other sys settings).
I would have to synchronize all Tests which are using sys.stdout.
How is it that you're not already getting garbage output with multiple threads calling print() and not using locks? ISTM that is the classic demonstration of why locks or queues a needed for shared resources.
To simplify synchronizing the print tests, consider writing a decorator that adds a shared resource lock to tests that print.
@shared_print
def test_reportgen(self):
...
Another possible solution is simpler but more blunt. Save the original stdout before any threads are launched and then shadow the builtin print function at the top of each test file:
from start_up import original_stdout
def print(*args, **kwds):
"Locking version of print()"
__builtins__.print(*args, file=original_stdout, **kwds)
Every test which is using print could use the wrong output.
I see no way how redirect_stdout can be used in a multi threaded environment.
More precisely, redirect_stdout creates a race condition if used in a thread that is running concurrently with threads that call print(). Of course, the same would happen with manual redirection, sys.stdout = output_file. And even without redirection, print calls in concurrent threads can step on top of one another. ISTM that printing to stdout is intrinsically in need of synchronization regardless of redirection.
I assumed that it would be very difficult to change it and that it wouldn't be worth the effort, but I wanted to ask because I didn't know for sure.
I'm capturing the output in my plugin to format the code with black. I will most likely have to rewrite this part, which leads to other problems, but probably easier to solve.
Changing sys.stdout behavior, such as making it thread-local, doesn't seem like a good idea to me either, but I think we can add functionality to contextlib.redirect_stdout to make it work better with pytest-run-parallel and the similar functionality (gh-127933) we're using to test CPython.
I think it's probably best implemented in an external library, possibly pytest-run-parallel, at least at first.
Here's a rough sketch of the idea:
contextlib.redirect_stdout gains an optional argument per_thread=False
When called with per_thread=True, contextlib.redirect_stdout wraps the passed stream with a PerThreadStream, and sets sys.stdout to the instance of PerThreadStream
PerThreadStream has a slot per-thread, along with a default stream. The PerThreadStream delegates file operations (like write()) the appropriate stream, or to the default stream if no stream is set for the current thread.
contextlib.redirect_stdout calls are synchronized with each other, but not necessarily with print() or sys.stdout.write() calls.
Calls to contextlib.redirect_stdout(..., per_thread=True) will set the current thread's stream slot, if sys.stdout is already a PerThreadStream
The last __exit__ from contextlib.redirect_stdout() removes the PerThreadStream from sys.stdout if all per-thread slots are empty, and restores sys.stdout to the default stream.
Reacted by Peter Bierma, Matthieu Rigal and Frank Dana
Note that pytest has its own capture mechanism, the capsys fixture, which is typically how stdout/stderr are captured in tests. The test in the OP, written as idiomatic pytest, would become:
I haven't looked at the capsys code to see what it uses, but I assume it's not threadsafe because, as said, pytest doesn't use threads. So I guess I'm saying,
Whatever the outcome is here / elsewhere, it's worth checking to make sure capsys is included.
Meaning, if it uses contextlib and @colesbury's suggestion is implemented, users will need some method of including per_thread=True in the calls capsys makes. If it does something else, or the issue is addressed some other way, a similar fix might be needed in pytest.
Activity
picnixz commentedon Feb 15, 2025
I'm not sure we can do it. AFAIK, those standard streams are at the interpreter state level. I also don't know why we only have a single write and not more. It's as if one thread is not doing anything or something was not caught at all (I would not be surprised if for instance we had more "hello" but I'm surprised we only have one)
cc @ZeroIntensity
15r10nk commentedon Feb 15, 2025
The reason for the one hello is that the second thread changes sys.stdout and this thread has more than two hellos. But I get only the first assertion with one hello.
picnixz commentedon Feb 15, 2025
Ah yes I see! ok nvm it was dumb of me.
[-]contextlib.redirect_stdout/stderr does not work with freethreading[/-][+]contextlib.redirect_stdout/stderr does not work with `pytest-run-parallel`[/+]ZeroIntensity commentedon Feb 15, 2025
I don't think so, at least without making it a huge breaking change. If we were to make
sys.stdout/sys.stderrthread-local, we'd break all programs that currently rely on that for catching data from all threads. In theory, I guess we could have some sort of opt-in system to makestdoutandstderrthread-local using some dirty hacks, but it would be a lot of work and I'm not convinced it's worth it. I'd just suggest synchronizing your tests that useredirect_stdoutwith a lock.15r10nk commentedon Feb 15, 2025
That is what I expected, but I wanted to ask.
I would have to synchronize all Tests which are using
sys.stdout. Every test which is using print could use the wrong output.I see no way how redirect_stdout can be used in a multi threaded environment.
devdanzin commentedon Feb 15, 2025
Playing with the code here, I stumbled on a segfault in a free-threaded
JITbuild, that's #130163.rhettinger commentedon Feb 18, 2025
FWIW,
redirect_stdoutwas never expected to work across threads, even in a build with a GIL.Stdout being global state is an inconvenient fact-of-life (much like locale or the various other sys settings).
How is it that you're not already getting garbage output with multiple threads calling
print()and not using locks? ISTM that is the classic demonstration of why locks or queues a needed for shared resources.To simplify synchronizing the print tests, consider writing a decorator that adds a shared resource lock to tests that print.
Another possible solution is simpler but more blunt. Save the original stdout before any threads are launched and then shadow the builtin print function at the top of each test file:
More precisely,
redirect_stdoutcreates a race condition if used in a thread that is running concurrently with threads that callprint(). Of course, the same would happen with manual redirection,sys.stdout = output_file. And even without redirection, print calls in concurrent threads can step on top of one another. ISTM that printing to stdout is intrinsically in need of synchronization regardless of redirection.15r10nk commentedon Feb 18, 2025
Because I use it in a pytest plugin and pytest does not use threads. I got this problem when I tried to make my code thread-safe by using pytest-run-parallel.
I assumed that it would be very difficult to change it and that it wouldn't be worth the effort, but I wanted to ask because I didn't know for sure.
I'm capturing the output in my plugin to format the code with black. I will most likely have to rewrite this part, which leads to other problems, but probably easier to solve.
rhettinger commentedon Feb 18, 2025
I suggest closing this.
colesbury commentedon Feb 18, 2025
Changing
sys.stdoutbehavior, such as making it thread-local, doesn't seem like a good idea to me either, but I think we can add functionality tocontextlib.redirect_stdoutto make it work better withpytest-run-paralleland the similar functionality (gh-127933) we're using to test CPython.I think it's probably best implemented in an external library, possibly
pytest-run-parallel, at least at first.Here's a rough sketch of the idea:
contextlib.redirect_stdoutgains an optional argumentper_thread=Falseper_thread=True,contextlib.redirect_stdoutwraps the passed stream with aPerThreadStream, and setssys.stdoutto the instance ofPerThreadStreamPerThreadStreamhas a slot per-thread, along with a default stream. ThePerThreadStreamdelegates file operations (likewrite()) the appropriate stream, or to the default stream if no stream is set for the current thread.contextlib.redirect_stdoutcalls are synchronized with each other, but not necessarily withprint()orsys.stdout.write()calls.contextlib.redirect_stdout(..., per_thread=True)will set the current thread's stream slot, ifsys.stdoutis already aPerThreadStream__exit__fromcontextlib.redirect_stdout()removes thePerThreadStreamfromsys.stdoutif all per-thread slots are empty, and restoressys.stdoutto the default stream.ferdnyc commentedon Sep 10, 2025
Note that pytest has its own capture mechanism, the
capsysfixture, which is typically how stdout/stderr are captured in tests. The test in the OP, written as idiomatic pytest, would become:I haven't looked at the
capsyscode to see what it uses, but I assume it's not threadsafe because, as said, pytest doesn't use threads. So I guess I'm saying,Whatever the outcome is here / elsewhere, it's worth checking to make sure
capsysis included.Meaning, if it uses
contextliband @colesbury's suggestion is implemented, users will need some method of includingper_thread=Truein the callscapsysmakes. If it does something else, or the issue is addressed some other way, a similar fix might be needed in pytest.Liam-DeVoe commentedon Jan 19, 2026
I PR'd an implementation of
redirect_stdout(..., per_thread: bool = False)based on @colesbury's design here: Quansight-Labs/pytest-run-parallel#167.(There are some differences to account for mixing
per_thread=Trueandper_thread=False).