Skip to content

compat: hono SIGSEGV on exit when stdout is redirected #1023

Description

@proggeramlug

Summary

After PRs #1015 (small-handle pointer guard in native_object_to_v8) and #1016 (Response/Request/Headers globals in V8 fallback), hono's compat sweep fixture runs to completion under a TTY but SIGSEGVs (rc=139) when stdout is redirected. Same binary, identical command line — only the stdout target differs.

cd /tmp/perry-compat-sweep/hono
./out                                  # exit=0, no visible output (TTY-attached, output may be buffered/lost)
./out > /tmp/x.txt 2> /tmp/y.txt       # exit=139, empty files
./out | cat                             # exit=139

The compat sweep harness redirects stdout into perry-out.txt, which triggers the crash and flags hono as FAIL_RUNTIME.

Fixture

/tmp/perry-compat-sweep/hono/entry.ts:

import { Hono } from "hono";
const app = new Hono();
app.get("/ping", (c) => c.text("pong"));
const port = 18933;
async function main() {
    const req = new Request(`http://localhost:${port}/ping`);
    const res = await app.fetch(req);
    const body = await res.text();
    console.log("body=" + body);
}
main();

Expected: body=pong. Actual under redirect: rc=139 with no output reaching the file.

Probable causes (untriaged)

The TTY-vs-redirect dependency points to one of:

  1. stdout buffering at exit. When stdout is a TTY, libc flushes line-by-line; under redirect it's fully buffered and only flushes at exit. A destructor running between the final console.log and the actual flush is hitting an invalid pointer.
  2. V8 cleanup ordering. The V8 isolate's exit-time teardown may free objects that something else still holds. The TTY path may give the runtime a chance to drain async work that the redirected path doesn't.
  3. process.exit not being called. When stdout is a TTY, the runtime may rely on user-visible flush before the bad teardown runs; under redirect the teardown happens earlier in the lifecycle.
  4. GC sweep at shutdown touching a freed V8 handle (relates to PR fix(jsruntime): hono SIGSEGV — guard small-handle pointers in native_object_to_v8 #1015's small-handle work — there may be another path missed).

Investigation starters

# Capture a backtrace via lldb when stdout is redirected:
cd /tmp/perry-compat-sweep/hono
lldb -- ./out > /tmp/hono.log
# inside lldb: 'run' then 'bt' after crash

# Or core-dump:
ulimit -c unlimited
./out > /tmp/x.txt 2> /tmp/y.txt
ls /cores/

# Compare with diagnostics on:
RUST_BACKTRACE=full PERRY_LOG_TRACE=1 PERRY_DIAG=1 ./out > /tmp/x.txt 2> /tmp/y.txt

Look at:

  • crates/perry-jsruntime/src/lib.rs — process shutdown, isolate disposal
  • crates/perry-runtime/src/gc.rs — sweep at exit
  • crates/perry-jsruntime/src/interop.rs — pump exit path
  • The Drop impls on JsRuntimeState, NodeModuleLoader, etc.

Relation to other issues

This may be a sibling of the busy-wait async lowering issue tracked as a separate parity issue (#1021): hono's await app.fetch(req) chain doesn't pump cleanly under the busy-wait, and the result might be that the program reaches "end of main" before the awaited continuation completes. Under TTY the buffering masks this; under redirect the cleanup races and segfaults.

If the async-lowering fix lands first, this issue might evaporate. Worth re-testing hono after any change to js_run_jsruntime_pump or native_callback_trampoline.

Workarounds in scope until fixed

  • Sweep harness could use script -q /dev/null ./out (give the program a pty) to mask the issue — discouraged because it hides the real bug.
  • Users hitting this manually can re-run with ./out | tee /tmp/out.log — same redirect crash, no escape.

Impact

Blocks hono from passing the sweep. Hono otherwise works end-to-end interactively. Once stdout-redirect crash is fixed, hono should be a clean PASS.

Metadata

Metadata

Assignees

No one assigned

    Labels

    parityCompatibility gap with Node.js, ECMAScript, or the supported ecosystem

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions