From 449fa2b3b567dd17b23ac8de00334fc14c31ca96 Mon Sep 17 00:00:00 2001 From: Robert Grizzell Date: Sun, 30 Aug 2026 20:57:59 -0500 Subject: [PATCH] SimSerial: line-buffer stdout so log lines flush promptly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SimSerial routes Serial output to stdout via putchar(). When stdout is not a TTY (a pipe, a file, or under a service manager), C stdio defaults to full buffering, so log lines don't appear until a ~4 KB buffer fills — forcing callers to wrap the process in `stdbuf -oL`. Set stdout to line-buffered once, at the first SimSerial construction (the global `Serial` is built at static-init, before any output), so each Serial.print line is flushed on its newline and reaches the terminal / journald promptly. Guarded to run once: setvbuf() is undefined after I/O has begun, and extra SimSerial instances may be constructed later (e.g. tests). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01KzBaSxeXYbJBJ7EYyfR3Lb --- cores/ardulinux/linux/LinuxSerial.cpp | 19 +++++++++++++++++++ cores/ardulinux/linux/LinuxSerial.h | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/cores/ardulinux/linux/LinuxSerial.cpp b/cores/ardulinux/linux/LinuxSerial.cpp index 8cc349d..beed04b 100644 --- a/cores/ardulinux/linux/LinuxSerial.cpp +++ b/cores/ardulinux/linux/LinuxSerial.cpp @@ -262,6 +262,25 @@ namespace arduino { // --- SimSerial: stdout-backed simulated serial (used as the console) --- + /** + * Line-buffer stdout the first time a SimSerial is constructed. + * + * The global `Serial` instance is constructed at static-init, before any + * output, so this runs before the first write. With line buffering, each + * log line is flushed on its terminating newline, so `Serial.print(...)` + * output reaches the terminal / journald promptly without wrapping the + * process in `stdbuf -oL`. Guarded to run only once: setvbuf() is undefined + * once I/O has occurred, and additional SimSerial instances may be created + * (e.g. in tests) after output has already started. + */ + SimSerial::SimSerial() { + static bool configured = [] { + setvbuf(stdout, nullptr, _IOLBF, 0); + return true; + }(); + (void)configured; + } + /** No-op: simulated port requires no hardware setup. */ void SimSerial::begin(unsigned long baudrate, uint16_t config) {} diff --git a/cores/ardulinux/linux/LinuxSerial.h b/cores/ardulinux/linux/LinuxSerial.h index 5dbb12a..1f435a8 100644 --- a/cores/ardulinux/linux/LinuxSerial.h +++ b/cores/ardulinux/linux/LinuxSerial.h @@ -114,6 +114,12 @@ namespace arduino { */ class SimSerial : public HardwareSerial { public: + /** + * Line-buffer stdout (once, at static init) so log lines reach the + * terminal / journald promptly, without needing an external + * `stdbuf -oL` wrapper around the process. + */ + SimSerial(); /** No-op; the simulated port is always "open". */ virtual void begin(unsigned long baudrate) { begin(baudrate, SERIAL_8N1); } /** No-op. */