Build log freezes when stdout is redirected: non-TTY output is block-buffered and never flushed (supersedes #16) #18
Labels
No labels
bug
claude:done
claude:failed
claude:in-progress
claude:ready
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Catcrafts/Crafter.Build#18
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
When
crafter-build's stdout is redirected to a file (not a TTY), progress and completion output is block-buffered and never flushed, so the log file freezes mid-build and the final lines (Built N steps in …ms,listening on port :…) never appear. Under-r(serve mode) the process then sits in its serve loop forever, so the trailing buffer is never flushed — the log looks permanently stuck even though the build finished seconds ago and the server is already answering.Important: this supersedes #16 — #16 was filed on wrong information
Issue #16 ("crafter-build -r hangs at step 101/139, no compiler running") was misdiagnosed by me. There is no build deadlock. What looked like a "hung driver with no compiler children frozen at
[100/139]" was simply a finished build sitting in the-rserve loop — idle driver + only the dev-server child + no compilers is exactly what success looks like. The "frozen log" was this stdout-buffering bug, not a stalled build.Proof: while the log read 0 bytes (or was frozen at
[100/139]),curl http://localhost:8080/returned HTTP 200 andbin/<target>/3DForts.wasm(6.7 MB) was already on disk and being served (the app even rendered in-browser with WebGPU).Please review what PR #17 (
fix: scope per-build module-state reset to the config being built, merged for #16) actually changed. It was written against the phantom-deadlock description, so either it's a no-op for a real problem, or it changed behaviour for reasons that don't match a genuine bug. Worth confirming it didn't introduce a regression or mask something — and whether it should stay, be reverted, or be re-justified on its own merits.Root cause (with code references)
implementations/Crafter.Build-Progress.cppis correctly TTY-aware, but only the TTY path flushes:std::fflush(stdout);std::fflush(stdout);std::println("[{}/{}] {}", done, total, label_);Finalize()— NO flush:std::println("Built {} step{} in {}ms", …);implementations/Crafter.Build-Clang.cpp:std::println("listening on port :{}", port);— NO flushstd::printlnwrites through Cstdout, which is fully (block) buffered when stdout is a regular file. With no flush on the non-TTY path, the[N/M]lines accumulate and only spill at ~4 KB block boundaries (≈ why it froze around step 100), and the finalBuilt…/listening…lines stay stuck in the trailing buffer that-rnever flushes.Fix (one line)
Line-buffer stdout when it isn't a terminal. Put it as the first statement in
main()(must run before any stdout write):Now every
\nflushes, so[N/M],Built N steps, andlistening on portall reach a redirected log immediately. No behaviour change on a TTY (that path already flushes).g_isTtyis already computed inSetVerbosity(line 105), butsetvbufmust precede any output, somain()is the correct home — notSetVerbosity.A narrower alternative is
std::fflush(stdout)at the three print sites above, but that spans two files and is easy to miss for futureprintlncalls; thesetvbufapproach is more robust.Impact
Any tooling that redirects the build log and waits for a completion/readiness marker (
Built … steps,listening on port,[N/N]) hangs forever, even though the build is done and the server is up. This has repeatedly stalled an automated runner that polled the log instead of the server.Risk of the fix: negligible — one flush per line is nothing next to spawning compilers, and a line-buffered redirected log is strictly an improvement.
Fix is up as PR #19 and is green + mergeable.
stdoutis block-buffered with no flush on the append-only progress path,Finalize(), and the-rlistening on portline. Hidden on normal exit (runtime flushes at exit), fatal under-r(process never exits → trailing buffer never flushes).setvbuf(stdout, _IOLBF)at the top ofmain()when stdout isn't a TTY. Kept self-contained in main.cpp (system headers only) because the self-host exe build resolvesCrafter.Buildagainst the installed/cached module BMIs, which shadow the freshly built local ones — a new exported interface symbol isn't visible until reinstall.examples/wasiproject under-r, stdout redirected, server left running: old binary → log 0 bytes whilecurlreturns HTTP 200; fixed binary → all lines (Built 4 steps…,listening on port :8080) present.crafter-build test: 12 passed.compiledatomic (not a no-op), with a deterministic regression test. Its only flaw is attribution — it credited itself with the observed #16 hang, which was actually this buffering bug. Details in the PR.Merge is owner-gated (the bot has
write, not merge rights on protectedmaster— same as #17, which was owner-merged). PR #19 is ready to merge.