This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: read(1) blocks on unflushed output
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.4
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: Sworddragon, akira, eryksun, vstinner
Priority: normal Keywords:

Created on 2014-09-19 14:08 by Sworddragon, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
test.py Sworddragon, 2014-09-19 14:13
Messages (13)
msg227097 - (view) Author: (Sworddragon) Date: 2014-09-19 14:08
On reading the output of an application (for example "apt-get download firefox") that dynamically changes a line (possibly with the terminal control character \r) I have noticed that read(1) does not read the output until it has finished with a newline. This happens even with disabled buffering. In the attachments is a testcase for this problem. Also here are 2 screenshots to compare the results:

Direct call: http://picload.org/image/crldgri/normal.png
Subprocess: http://picload.org/image/crldgrw/subprocess.png
msg227098 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2014-09-19 14:10
> On reading the output of an application (for example "apt-get download firefox") that dynamically changes a line (possibly with the terminal control character \r) I have noticed that read(1) does not read the output until it has finished with a newline.

The buffering of stdout and/or stderr of your application probably changes if the application runs in a terminal (TTY) or if the output is redirected to a pipe (not a TTY). Set the setvbuf() function.

You can try my hack to disable buffering using LD_PRELOAD:
https://bitbucket.org/haypo/misc/src/4d133ea3e46550808305b093557ee51d2de2ac9f/misc/nobuffer.c?at=default
msg227100 - (view) Author: (Sworddragon) Date: 2014-09-19 14:13
Edit: Updated testcase as I forgot to flush the output (in case somebody hints to it).
msg227102 - (view) Author: (Sworddragon) Date: 2014-09-19 14:26
> The buffering of stdout and/or stderr of your application probably
> changes if the application runs in a terminal (TTY) or if the output is
> redirected to a pipe (not a TTY). Set the setvbuf() function.

This means in the worst case there is currently no official way to get this output before it writes a newline?


> You can try my hack to disable buffering using LD_PRELOAD:
> https://bitbucket.org/haypo/misc/src/4d133ea3e46550808305b093557ee51d2de2ac9f/misc/nobuffer.c?at=default

I will try later if I can successfully compile Python with this hack.
msg227103 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2014-09-19 14:41
> This means in the worst case there is currently no official way to get this output before it writes a newline?

The behaviour of stdout/stderr is defined in the C library, see setvbuf() manual for more information. I don't know a generic way to change the default buffering without modifying the application (or the LD_PRELOAD hack).

> I will try later if I can successfully compile Python with this hack.

You don't need to compile Python. Just compile nobuffer.c to libnobuffer.so. See the "documentation" in nobuffer.c.
msg227104 - (view) Author: (Sworddragon) Date: 2014-09-19 16:22
> You don't need to compile Python. Just compile nobuffer.c to
> libnobuffer.so. See the "documentation" in nobuffer.c.

Strictly following the documentation does not work:

sworddragon@ubuntu:~/tmp$ gcc -shared -o nobuffer.so interceptor.c
gcc: error: interceptor.c: No such file or directory
gcc: fatal error: no input files
compilation terminated.


Trying to fix this results in this error:

sworddragon@ubuntu:~/tmp$ gcc -shared -o nobuffer.so nobuffer.c
/usr/bin/ld: /tmp/ccgArKHv.o: relocation R_X86_64_PC32 against undefined symbol `stdout@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status
msg227131 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2014-09-19 23:20
Ah yes, try "gcc -shared nobuffer.c -o libnobuffer.so -fPIC".
msg227133 - (view) Author: (Sworddragon) Date: 2014-09-19 23:56
I was able to compile the library but after executing "LD_PRELOAD=./libnobuffer.so ./test.py" I'm seeing no difference. The unflushed output is still not being read with read(1).
msg227134 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2014-09-20 00:02
stdbuf is the typical way to apply the LD_PRELOAD trick:

https://www.gnu.org/software/coreutils/manual/html_node/stdbuf-invocation.html

There's also the "unbuffer" expect script:

http://expect.sourceforge.net/example/unbuffer.man.html
msg227135 - (view) Author: (Sworddragon) Date: 2014-09-20 00:10
"stdbuf -o 0 ./test.py" and "unbuffer ./test.py" doesn't change the result too. Or is something wrong with my testcase?
msg227142 - (view) Author: Akira Li (akira) * Date: 2014-09-20 03:20
Related: http://stackoverflow.com/questions/25923901/last-unbuffered-line-cant-be-read

Make sure you follow the links in the comments.
msg227147 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2014-09-20 06:56
unbuffer works for me. It fakes a tty, so apt-get doesn't automatically enter quiet mode.

    import io
    import subprocess

    args = ['unbuffer', 'apt-get', 'download', 'firefox']
    p = subprocess.Popen(args, 
                         stdout=subprocess.PIPE, 
                         stderr=subprocess.STDOUT)
    out = io.TextIOWrapper(p.stdout, newline='')

    while True:
        c = out.read(1)
        if c == '':
            break
        print(c, end='', flush=True)

    p.wait()

unbuffer isn't required if you disable quiet mode as follows:

    args = ['apt-get', '-q=0', 'download', 'firefox']
msg227226 - (view) Author: (Sworddragon) Date: 2014-09-21 19:26
It works if "-q 0" is given without the need of a workaround. So this was just a feature of apt that was causing this behavior. I think here is nothing more to do so I'm closing this ticket.
History
Date User Action Args
2022-04-11 14:58:08adminsetgithub: 66633
2014-09-21 19:26:13Sworddragonsetstatus: open -> closed
resolution: not a bug
messages: + msg227226
2014-09-20 06:56:37eryksunsetmessages: + msg227147
2014-09-20 03:20:56akirasetnosy: + akira
messages: + msg227142
2014-09-20 00:10:53Sworddragonsetmessages: + msg227135
2014-09-20 00:02:25eryksunsetnosy: + eryksun
messages: + msg227134
2014-09-19 23:56:12Sworddragonsetmessages: + msg227133
2014-09-19 23:20:29vstinnersetmessages: + msg227131
2014-09-19 16:22:10Sworddragonsetmessages: + msg227104
2014-09-19 14:41:05vstinnersetmessages: + msg227103
2014-09-19 14:26:36Sworddragonsetmessages: + msg227102
2014-09-19 14:13:50Sworddragonsetfiles: + test.py

messages: + msg227100
2014-09-19 14:13:06Sworddragonsetfiles: - test.py
2014-09-19 14:10:59vstinnersetnosy: + vstinner
messages: + msg227098
2014-09-19 14:08:08Sworddragoncreate