Compare commits

...

7 Commits

Author SHA1 Message Date
Jarred Sumner
444827a5e1 Delete test/js/bun/spawn/spawn-flush-stdio-on-exit.test.ts 2026-03-09 19:25:32 -07:00
Claude Bot
bec442e3ac test: move flush-stdio test to test/js/bun/spawn/
Move from test/regression/issue/ (which expects a real issue number) to
test/js/bun/spawn/ where other spawn/exit tests live.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 02:17:26 +00:00
Jarred Sumner
d776a09b6d Delete pointless test. Existing tests cover. 2026-03-09 19:12:28 -07:00
Claude Bot
c39b81f41d test: add regression test for stdio flush before TerminateProcess
Spawns bun with console.log/console.error + process.exit() and asserts
that stdout/stderr output is not lost. Exercises the Bun__flushCStdio
path on Windows where TerminateProcess skips CRT teardown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 02:08:20 +00:00
Claude Bot
24941d1aaa ci: re-trigger build
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 01:55:23 +00:00
Jarred Sumner
2963bf4cf1 Merge branch 'main' into claude/flush-stdio-before-terminate-process 2026-03-09 18:53:16 -07:00
Claude Bot
3861518acc fix(windows): flush C stdio buffers before TerminateProcess
TerminateProcess skips CRT teardown, so buffered stdout/stderr output
can be lost. Add Bun__flushCStdio() in c-bindings.cpp that calls
fflush(stdout) and fflush(stderr), and invoke it from both exit paths
that use TerminateProcess (Global.exit and reloadProcess).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-10 01:52:36 +00:00
3 changed files with 23 additions and 0 deletions

View File

@@ -93,6 +93,10 @@ pub fn runExitCallbacks() void {
on_exit_callbacks.items.len = 0;
}
/// Flush the C runtime stdout/stderr buffers. Must be called before
/// TerminateProcess on Windows because it skips CRT teardown.
pub extern "c" fn Bun__flushCStdio() void;
var is_exiting = std.atomic.Value(bool).init(false);
export fn bun_is_exiting() c_int {
return @intFromBool(isExiting());
@@ -130,6 +134,10 @@ pub fn exit(code: u32) noreturn {
// TerminateProcess skips DLL cleanup entirely, matching the
// behavior on Linux where quick_exit() also skips library
// teardown. Bun's own cleanup has already run via Bun__onExit().
//
// TerminateProcess does not run CRT teardown, so we must
// explicitly flush the C stdio buffers to avoid losing output.
Bun__flushCStdio();
const rc = std.os.windows.kernel32.TerminateProcess(
std.os.windows.kernel32.GetCurrentProcess(),
code,

View File

@@ -384,6 +384,18 @@ extern "C" ssize_t pwritev2(int fd, const struct iovec* iov, int iovcnt,
#endif
extern "C" void Bun__onExit();
#if OS(WINDOWS)
/// Flush the C runtime stdout/stderr buffers. Called before TerminateProcess
/// which, unlike ExitProcess, does not run CRT teardown and therefore would
/// otherwise lose any buffered output.
extern "C" void Bun__flushCStdio()
{
fflush(stdout);
fflush(stderr);
}
#endif
extern "C" int32_t bun_stdio_tty[3];
#if !OS(WINDOWS)
static termios termios_to_restore_later[3];

View File

@@ -1506,6 +1506,9 @@ pub fn reloadProcess(
if (comptime Environment.isWindows) {
// on windows we assume that we have a parent process that is monitoring us and will restart us if we exit with a magic exit code
// see becomeWatcherManager
//
// Flush C stdio buffers before TerminateProcess since it skips CRT teardown.
Global.Bun__flushCStdio();
const rc = c.TerminateProcess(c.GetCurrentProcess(), windows.watcher_reload_exit);
if (rc == 0) {
const err = bun.windows.GetLastError();