GeezerVison is showing github.com — Hangup when printing empty string to unbuffered stdout · Issue #148773 · python/cpython
Hangup when printing empty string to unbuffered stdout #148773Labels OS-unsupportedinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)topic-IOtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error DescriptionCrash report
What happened?
$ sudo python3 -u -c 'print(""); print("foo")'
Hangup
When printing an empty string to unbuffered stdout, under certain circumstances (e.g. when running on AIX under sudo, screen, tmux), the user session including the Python interpreter will terminate.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Other
Output from running 'python -VV' on the command line:
Python 3.12.13 (main, Mar 6 2026, 03:56:12) [GCC 10.3.0]
Linked PRs
Activitycmaloney commented on Apr 19, 2026 Hi! Thanks for the issue. Reading through the PR a little bit it looks like the issue here is that the first write is of length 0 and returns 0 and the I/O stack continues after that rather than exiting?
Length 0 is a defined behavior in the write manual page, and _Py_write_impl underlies a lot of different I/O cases across many platforms so hesitant to skip the write call. For this particular case it should be going through Buffered I/O and Text I/O which I would have expected to not do zero-length writes....
Really hesitant to change the underlying primitive as things like zero return and 0 length is allowed and can have side effects. I am potentially open to making sure Buffered I/O doesn't call through in this case; its job is to avoid too many writes. sideeffect42 commented on Apr 20, 2026 Hi @cmaloney.
Thank you for having a look at my PR.
The issue is that the "I/O stack" tells the other side (the terminal) that it has hung up instead of doing nothing an continuing.
Can you please quote the part of the write manual page which you say says length 0 is defined behaviour?
In the manual pages for write(3) I've read it says:
Before any action described below is taken, and if nbyte is zero and the file is a regular file, the write() function may detect and return errors as described below. In the absence of errors, or if error detection is not performed, the write() function shall return zero and have no other results. If nbyte is zero and the file is not a regular file, the results are unspecified.
IOW: if it is writing to a regular file it may do some error checking (if it wants to), but must return 0 and have no other results (I read this as: no side effects).
When writing to anything else it can do whatever it wants.
Buffered I/O does not perform the write syscall for the empty string (only for the line delimiter), however unbuffered I/O (PYTHONUNBUFFERED) does, but is undefined behaviour. cmaloney commented on Apr 21, 2026 From that man page quote: "the results are unspecified" which is distinct from undefined behavior.
To be specific for this case: _Py_write is used to write to a almost every file kind across all the platforms (Windows, BSD, Linux, macOS, ...) Python ships currently. It is not guaranteed to be writing to a "regular file" (and regularly is not for various tools/utilities built on Python such as talking to I/O devices). For terminal typlically the I/O is a Pipe or TTY rather than a regular file. Unspecified just means there there is not one particular behavior that always happens.
The PYTHONUNBUFFERED explains a bit. That flag introduces a lot of additional edge cases in the I/O stack. With it rather than a TextIOWrapper + BufferedIO + FileIO you get just a TextIOWrapper with some config + FileIO. I really wish we had a BufferedIO with zero internal byte storage for the case instead, as FileIO does not behave like other I/O primitives Python programmers are used to. For example it drops partially written buffers rather than retrying or raising an exception. The retries and exceptions are generally added by BufferedIO. Things generally "work" with TextIOWrapper + FileIO but not always. In Python 3.16 I'm hoping to get to always a BufferedIO (but a zero length buffer one) so we can get the advantages of PYTHONUNBUFFERED / open(buffering=0) without the drawbacks.
In your particular case it sounds a lot like a TTY where each individual data packet sent gets received (and the received length 0 is misinterpreted as end of stream). FileIO (https://docs.python.org/3/library/io.html#io.FileIO) just above the _Py_write in this case I think guarantees a single write here and isn't somewhere we can elide the write of length zero either. TextIOWrapper in this case might be okay to do it at but would need to see the complexity it adds. sideeffect42 commented on Apr 21, 2026 I came to exactly the same conclusion as you describe. The write("") is interpreted (rightfully?) as EOF on the other end.
When I compare the difference in syscalls generated between "regular PYTHONBUFFERED" and PYTHONUNBUFFERED, it is that the first does not generate the write("") syscall while the unbuffered does.
My line of thinking was to catch empty writes at the lowest layer so that we don't have to check all the different code paths whether they catch this case. And there might be new ones added which would have to be checked as well.
However, I see your concerns. If your plan is anyway to add a zero-length BufferedIO into the unbuffered I/O stack (and it catches the empty writes which I think it does in buffered mode already), then I think it should address this bug as well which would work for me. cmaloney commented on Apr 23, 2026 For 3.16 I'd really like to (far too late for my I/O plans in 3.15). In general I want open(buffer_size=0) to use BufferedIO as a layer for its behaviors (without buffer space/copy). Overarching thread for those improvements is: https://discuss.python.org/t/reworking-buffered-i-o-in-cpython/90019
I think it's useful to keep this open / the unnecessary write of size zero in PYTHONUNBUFFERED is a good extra test case to make sure that rework solves.
MetadataMetadataOS-unsupportedinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)topic-IOtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error StatusShow more project fields
DevelopmentNo branches or pull requestsIssue actions
|