fix: line-buffer stdout when redirected so progress/readiness lines flush (#18) #19
No reviewers
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Catcrafts/Crafter.Build!19
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "claude/issue-18"
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 not a TTY (redirected to a file or pipe), the C runtime defaults to full (block) buffering. The progress path is TTY-aware and the in-place redraw flushes explicitly, but every non-TTY write goes through block-bufferedstdoutwith no flush:Crafter.Build-Progress.cpp— non-TTY append pathstd::println("[{}/{}] {}", …)(line 134)Finalize()—std::println("Built {} step{} in {}ms", …)(line 161)Crafter.Build-Clang.cpp—std::println("listening on port :{}", port)(line 1577)On a normal build this is hidden: the C runtime flushes
stdoutat process exit. Under-rthe process never exits — it blocks in its serve loop — so the trailing buffer is never flushed. A redirected log freezes mid-build (or sits at 0 bytes) even though the build finished seconds ago and the server is already answering HTTP 200. Any tooling polling the log forBuilt …/listening …/[N/N]hangs forever.This is the real cause of the "frozen log" misdiagnosed as a build deadlock in #16.
Fix
Switch
stdoutto line buffering at the very top ofmain(), before any output, only when stdout is not a terminal:Every
\nthen flushes, so the markers reach a redirected log immediately. No behaviour change on a TTY (already line-buffered; the redraw path flushes itself).Why self-contained in
main.cppand not aCrafter::Progressexport: the self-hosting exe build compilesmain.cpp(and the crafter-build sources themselves) against the installed/cachedCrafter.Buildmodule BMIs, which are listed on-fprebuilt-module-pathahead of — and therefore shadow — the freshly built local ones. A new interface symbol is invisible to the build untilcrafter-buildis reinstalled. Using only system headers (isatty+setvbuf) sidesteps that bootstrapping wall.Verification
Built the binary and ran the
examples/wasiproject under-rwith stdout redirected to a file, server left running (not exited):Old binary — log frozen while server answers:
This fix — all lines present while server answers:
crafter-build test— 12 passed.Re: PR #17 (requested review)
#18 asked me to re-check PR #17 (merged for #16), since #16's reported symptom was actually this buffering bug.
Recommendation: keep #17. It is not a no-op. The data race it fixes is genuine and independent of the buffering bug:
Configurationobjects are shared across the build DAG, each compiled concurrently by its ownBuild(), and the old recursive reset could clear a shared dependency'scompiledatomic from a parent/sibling thread after that dependency's compile thread set it and exited but before an intra-config waiter rancompiled.wait(false)— a permanent block. Scoping the reset to the current config removes that race, and the addedConcurrentDependencyResettest pins the invariant deterministically (passes here as part of the green suite).The only inaccuracy is attribution: #17's message credits it with fixing the observed #16 hang, which was in fact this stdout buffering. #17 stands on its own merits as a correctness fix; reverting would reintroduce the race. No regression observed — full suite green.
Resolves #18