Analysis follows. The `print' family of functions send their output to stdout by default when ran in emacs --batch mode. Similar to bug#46388 (27.1; emacs -batch does not output messages immediately when invoked outside of the command prompt) the issue stems from the expectation that stdout buffering should behave like in POSIX systems, which is not necessarily the case on 'windows-nt. The POSIX standard reads (https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdin.html) under "stderr, stdin, stdout - standard I/O streams": """ ... At program start-up, three streams shall be predefined and need not be opened explicitly: standard input (for reading conventional input), standard output (for writing conventional output), and standard error (for writing diagnostic output). When opened, the standard error stream is not fully buffered; the standard input and standard output streams are fully buffered if and only if the stream can be determined not to refer to an interactive device. ... """ stdout on windows-nt is either always unbuffered (when attached to the console) or fully-buffered (otherwise), while on 'gnu/linux I have experimentally found it to be line-buffered when invoked from the terminal or from another program such as Emacs M-x shell. I consider this line-buffered behavior of 'gnu/linux to fall under the "interactive device" of the standard mentioned above. (When stdout is redirected to a file on 'gnu/linux, I found stdout to be fully-buffered having a 4096 buffer size). (See standard-streams-test report @ https://github.com/ikappaki/standard-streams-test for a tool that was written to investigate the buffering behavior of stderr on MS-Windows, i.e. unbuffered when attached to console, fully buffered otherwise. The same tool was modified to test stdout on a similar manner, which yielded exactly the same result). Thus the difference in behavior as described in the bug report is due to stdout on 'windows-nt being fully buffered, rather than being line buffered as in 'gnu/linux. As such, two likely fixes are presented below, one that flushes stdout on a newline only iff stdout is attached to a pipe (as if it is the "interactive device" of the POSIX standard) but staying fully buffered otherwise (e.g. when output was redirected to a pipe or a socket), while the other fix always flushing stdout on a newline (a much simpler and less involved solution -- similar to the one in bug#46388 suggested by Mr. Eggert -- though not optimized for redirections to files). (Please note that the intention below is to change src/print.c:printchar_to_stream(), which unless I missed something, is only called from the `print' family of functions when Emacs is in non-interactive mode). _flush on newline when pipe_: #+begin_src diff 5 files changed, 40 insertions(+) src/print.c | 2 ++ src/sysdep.c | 21 +++++++++++++++++++++ src/sysstdio.h | 1 + src/w32.c | 13 +++++++++++++ src/w32.h | 3 +++ modified src/print.c @@ -234,6 +234,8 @@ printchar_to_stream (unsigned int ch, FILE *stream) if (ASCII_CHAR_P (ch)) { putc (ch, stream); + if (ch == '\n') + maybe_flush_stdout(); #ifdef WINDOWSNT /* Send the output to a debugger (nothing happens if there isn't one). */ modified src/sysdep.c @@ -2821,6 +2821,27 @@ errwrite (void const *buf, ptrdiff_t nbuf) fwrite_unlocked (buf, 1, nbuf, errstream ()); } +/* On windows, stdout is unbuffered when attached to the console but + fully buffered (4096 bytes) when redirected to a pipe (this buffer + is complementary to the pipe buffer). + + Since Emacs --batch, at least on Windows, does not flush stdout it + means printing to standard output (for example with `princ' and its + variants) will not reach the parent process until at least this + process exits or the stream buffer is full, resulting to a very + poor interaction with the parent, contrary to how 'gnu/linux stdout works. + + We thus provide an interface to these functions to flush stdout + when has been redirected to a pipe. +*/ +void maybe_flush_stdout (void) +{ +#ifdef WINDOWSNT + if (is_stdout_pipe()) + fflush_unlocked(stdout); +#endif /* WINDOWSNT */ +} + /* Close standard output and standard error, reporting any write errors as best we can. This is intended for use with atexit. */ void modified src/sysstdio.h @@ -27,6 +27,7 @@ #define EMACS_SYSSTDIO_H #include "unlocked-io.h" extern FILE *emacs_fopen (char const *, char const *); +extern void maybe_flush_stdout (void); extern void errputc (int); extern void errwrite (void const *, ptrdiff_t); extern void close_output_streams (void); modified src/w32.c @@ -10190,6 +10190,12 @@ term_ntproc (int ignored) term_w32select (); } +static bool _is_stdout_pipe = false; +bool is_stdout_pipe (void) +{ + return _is_stdout_pipe; +} + void init_ntproc (int dumping) { @@ -10268,6 +10274,13 @@ init_ntproc (int dumping) _fdopen (2, "w"); } + HANDLE soh = (HANDLE)_get_osfhandle(_fileno(stdout)); + _is_stdout_pipe = + /* is pipe (anonymous, named or socket)*/ + GetFileType(soh) == FILE_TYPE_PIPE + /* and is definitely not a socket */ + && GetNamedPipeInfo(soh, NULL, NULL, NULL, NULL); + /* unfortunately, atexit depends on implementation of malloc */ /* atexit (term_ntproc); */ if (!dumping) modified src/w32.h @@ -170,6 +170,9 @@ #define FILE_SERIAL 0x0800 extern HANDLE maybe_load_unicows_dll (void); extern void globals_of_w32 (void); +/* return whether standard output is redirected to a pipe. */ +extern bool is_stdout_pipe (void); + extern void term_timers (void); extern void init_timers (void); #+end_src #+begin_src diff 1 file changed, 8 insertions(+) src/print.c | 8 ++++++++ modified src/print.c @@ -235,6 +235,14 @@ printchar_to_stream (unsigned int ch, FILE *stream) { putc (ch, stream); #ifdef WINDOWSNT + /* Flush stdout after outputting a newline since stdout is + fully buffered when redirected to a pipe, contrary to + POSIX when attached to an "interactive device" (see + "stderr, stdin, stdout - standard I/O streams" in IEEE + 1003.1-2017) */ + if (ch == '\n' && stream == stdout) + fflush_unlocked(stdout); + /* Send the output to a debugger (nothing happens if there isn't one). */ if (print_output_debug_flag && stream == stderr) #+end_src Feel free to modify/replace the above two as you find appropriate (especially the excessive comments). Thanks