Skip to content

Buffer a glibc guest's piped stdout fully from the first line - #18

Open
buyemune-sys wants to merge 2 commits into
pg83:mainfrom
buyemune-sys:fix/glibc-stdout-buffering
Open

buyemune-sys wants to merge 2 commits into
pg83:mainfrom
buyemune-sys:fix/glibc-stdout-buffering

Conversation

@buyemune-sys

Copy link
Copy Markdown

The problem

glibc settles stdout's buffering when the stream first allocates its buffer: line buffering for a terminal, full blocks for anything else. musl decides at the first flush (ext/musl/src/stdio/__stdout_write.c:8). Until then stdout counts as line buffered, so the first line written to a pipe or file goes out on its own, ahead of whatever the program writes to stderr next:

$ ./stdout_buffering 2>&1 | cat        # glibc
err 1
err 2
out 1
out 2
$ solo ./stdout_buffering 2>&1 | cat
out 1
err 1
err 2
out 2

That reorders merged output. LuaJIT's lj-586-debug-non-string-error test reads a debug.debug() session through such a pipe, and under solo it finds the printed value ahead of the prompt that preceded it.

The fix

The bridge's __libc_start_main (lib/glibc_shim.cpp:2604) makes glibc's decision before the guest's initializers run: if stdout is not a terminal, it sets full buffering. A guest's own setvbuf still overrides it.

Test

The guest executable writes guest stderr right after its first stdout line. The smoke run pipes stdout with stderr merged in and expects that line ahead of all of the guest's stdout.

On main: solo run flushed the guest's piped stdout line by line. With the fix: ./build test passes.

buyemune-sys and others added 2 commits October 20, 2026 15:42
Security updates replaced libapparmor1 4.0.1really4.0.1-0ubuntu0.24.04.7
with .9 and libexpat1 2.6.1-2ubuntu0.4 with ubuntu0.6, and the pool kept
only the new files, so the cold-cache Fetch x86_64 job answers 404 and
every x86-64 job behind it is skipped. Both pins move to the versions
noble-updates ships now; their SHA256s match the archive's Packages index.
The other twelve pins are still served.
glibc settles stdout's buffering when the stream first allocates its
buffer: line buffering for a terminal, full blocks for anything else.
musl decides at the first flush instead. Until then stdout counts as
line buffered, so the first line written to a pipe or file goes out on
its own, ahead of anything the program wrote to stderr after it.

That reorders merged output (`prog 2>&1 | ...`): LuaJIT's
lj-586-debug-non-string-error test reads a debugger session through such
a pipe and finds the printed value ahead of the prompt that preceded it.

The bridge's __libc_start_main now makes glibc's decision before the
guest's initializers run. A guest's own setvbuf still overrides it.

The guest executable writes to stderr after its first stdout line, and
the smoke run, which pipes stdout with stderr merged in, expects that
line ahead of all of the guest's stdout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@buyemune-sys
buyemune-sys force-pushed the fix/glibc-stdout-buffering branch from 74aa8d1 to a6bfc07 Compare October 20, 2026 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant