Skip to content
35 changes: 35 additions & 0 deletions apps/server/src/speech/native.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
// @effect-diagnostics nodeBuiltinImport:off - verifies isolated native worker processes and their exit events.
import * as NodeFS from "node:fs";
import * as NodeOS from "node:os";
import * as NodePath from "node:path";

import { expect, it } from "vite-plus/test";

import { loadNativeSpeechModel } from "./native.ts";
Expand Down Expand Up @@ -155,6 +159,37 @@ it("kills a hung streaming feed without waiting for native cleanup", async () =>
await model.dispose();
await rejected;
});

it("skips process exit teardown when the backend dies during native inference", async () => {
const directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3code-speech-orphan-"));
const marker = NodePath.join(directory, "exit-teardown");
// The exit hook stands in for native atexit teardown; the interval for the threadpool call.
const model = await loadNativeSpeechModel(
"unused.gguf",
new AbortController().signal,
streamingFixture(`async () => {
const { writeFileSync } = process.getBuiltinModule("node:fs");
process.once("exit", () => writeFileSync(${JSON.stringify(marker)}, "ran"));
setInterval(() => {}, 1 << 30);
process.disconnect();
return await new Promise(() => {});
}`),
);
try {
await model.begin({ timestamps: "none" }, false);
await expect(model.feed(new Float32Array(512).fill(0.25))).rejects.toThrow(
"Speech process exited",
);
expect(
NodeFS.existsSync(marker),
"orphaned worker ran exit teardown while native inference was in flight",
).toBe(false);
} finally {
await model.dispose();
NodeFS.rmSync(directory, { recursive: true, force: true });
}
});

const vadFixture = ({
streaming = false,
failVad = false,
Expand Down
3 changes: 2 additions & 1 deletion apps/server/src/speech/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ process.on("message", async (message) => {
error: error instanceof Error ? error.stack ?? error.message : String(error) });
}
});
process.on("disconnect", () => process.exit(0));
// Exit teardown under in-flight native inference segfaults, so terminate without it.
process.on("disconnect", () => process.kill(process.pid, "SIGKILL"));
`;

type Reply = {
Expand Down
Loading