Skip to content

ffi: bun:ffi symbol thunks are extern "C" — a use-after-close throw aborts on Linux (cargo-test still red after #8464) #8479

Description

@proggeramlug

cargo-test still fails on main after #8464: tier1_every_ffi_type_against_test_dylib aborts on Linux with

thread '<unnamed>' panicked at library/core/src/panicking.rs:225:5:
panic in a function that cannot unwind
thread caused non-unwinding panic. aborting.

Measured on the scheduled sweep of 15a30d7f6 (run 32353180323), which contains #8464 — so the closure-dispatch conversion did not fix this.

Where it actually aborts. The captured stdout stops right after external-terminated: 2 and never reaches closed-throws: — the step that deliberately throws:

lib.close();
try { s.ffi_i32_add1(1); } catch (e: any) { closedError = String(e && e.message); }
console.log("closed-throws:", closedError.includes("close()"));

That throw originates in bun_ffi::dlopen::invoke_from_closure, which calls throw_error_with_code("bun:ffi: symbol … was called after close() …"). It is reached through the per-arity stubs generated by sym_thunk!:

extern "C" fn $name(closure: *const ClosureHeader $(, $a: f64)*) -> f64 {
    unsafe { invoke_from_closure(closure, &args) }
}

Those stubs are extern "C", i.e. nounwind, so the unwind hits Rust's abort-on-unwind shim inside the thunk itself. #8464 made the closure-dispatch callers unwind-capable; these thunks are the callees it could not see — a call through an extern "C-unwind" function pointer does not remove the callee's own nounwind guard.

close_thunk has the same shape: it takes a Mutex and unwrap()s, and a poison panic there would abort identically.

Fix: generate both as extern "C-unwind" (PR #8479).

Process note worth recording. #8464 was merged while its own e2e-scoped job — the Linux run of exactly this suite, which the PR deliberately opted into by naming the test file — was still in flight; merging cancelled it (run 32340796570, job cancelled at 08:52). The check that would have caught this was destroyed by the merge. macOS passes this test with and without either fix, so the Linux e2e-scoped result is the only verdict, and it must be allowed to report before merging.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions