diff --git a/src/install/PackageManager.rs b/src/install/PackageManager.rs index 0225eed350d..f89944c6238 100644 --- a/src/install/PackageManager.rs +++ b/src/install/PackageManager.rs @@ -1414,10 +1414,15 @@ pub(crate) fn allocate_package_manager() { let ptr = bun_core::heap::into_raw(Box::::new_uninit()).cast::(); holder::RAW_PTR.store(ptr, core::sync::atomic::Ordering::Release); + #[cfg(bun_asan)] bun_core::add_exit_callback(deinit_caches_at_exit); } +#[cfg(bun_asan)] extern "C" fn deinit_caches_at_exit() { + if !bun_crash_handler::cli_state::is_main_thread() { + return; + } if !holder::INITIALIZED.load(core::sync::atomic::Ordering::Acquire) { return; } diff --git a/test/cli/install/bun-install-registry.test.ts b/test/cli/install/bun-install-registry.test.ts index 67ae7845fc2..4296dc08cea 100644 --- a/test/cli/install/bun-install-registry.test.ts +++ b/test/cli/install/bun-install-registry.test.ts @@ -260,6 +260,42 @@ describe("certificate authority", () => { expect(await exited).toBe(1); }); + test("non-existent --cafile with workspaces exits 1 without crashing", async () => { + // The workspace walk in `PackageManager::init()` populates the workspace + // package.json cache before the HTTP thread starts. When the HTTP thread + // then fails CA validation and drives process exit, the exit path must not + // tear down that main-thread-owned cache from the HTTP thread. + await Promise.all([ + write( + packageJson, + JSON.stringify({ + name: "foo", + version: "1.0.0", + workspaces: ["packages/*"], + dependencies: { "no-deps": "1.1.1" }, + }), + ), + ...["a", "b", "c"].map(name => + write( + join(packageDir, "packages", name, "package.json"), + JSON.stringify({ name: `pkg-${name}`, version: "1.0.0" }), + ), + ), + ]); + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install", "--cafile", "/does/not/exist"], + cwd: packageDir, + stderr: "pipe", + stdout: "pipe", + env, + }); + const out = await stdout.text(); + expect(out).not.toContain("no-deps"); + const err = await stderr.text(); + expect(err).toContain(`HTTPThread: could not find CA file: '/does/not/exist'`); + expect(await exited).toBe(1); + }); + test("cafile from bunfig does not exist", async () => { await Promise.all([ write(