From 31d8e046bf0ed215b8a89ba079fa4ab06a3c9729 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 5 Aug 2026 01:40:08 +0800 Subject: [PATCH] fix(llama-cpp): reject queued work after cleanup failure --- .../llama-cpp/src/inference-provider.test.ts | 14 ++++---- .../llama-cpp/src/inference-provider.ts | 35 +++++++++++++------ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/extensions/llama-cpp/src/inference-provider.test.ts b/extensions/llama-cpp/src/inference-provider.test.ts index dabfa648400f..26bc0959e268 100644 --- a/extensions/llama-cpp/src/inference-provider.test.ts +++ b/extensions/llama-cpp/src/inference-provider.test.ts @@ -858,14 +858,16 @@ describe("llama.cpp inference provider", () => { const otherModel = { ...model, id: "other.gguf", params: { modelPath: "other.gguf" } }; await collectTestEvents({ prompt: "one" }); await collectTestEvents({ selectedModel: otherModel, prompt: "two" }); - const cleanup = Promise.withResolvers(); - mocks.contextDispose.mockImplementationOnce(async () => await cleanup.promise); - const failedSwitch = await createTestStream({ prompt: "three" }); + let rejectCleanup!: (error: Error) => void; + const cleanup = new Promise((_resolve, reject) => { + rejectCleanup = reject; + }); + mocks.contextDispose.mockImplementationOnce(async () => await cleanup); + await createTestStream({ prompt: "three" }); await vi.waitFor(() => expect(mocks.contextDispose).toHaveBeenCalledTimes(2)); - const disposing = inferenceRuntime.dispose(); - cleanup.reject(new Error("context cleanup failed")); - await failedSwitch.result(); const unavailable = await createTestStream({ selectedModel: otherModel, prompt: "four" }); + const disposing = inferenceRuntime.dispose(); + rejectCleanup(new Error("context cleanup failed")); await expect(unavailable.result()).resolves.toMatchObject({ errorMessage: "llama.cpp runtime stopped after cleanup failed", }); diff --git a/extensions/llama-cpp/src/inference-provider.ts b/extensions/llama-cpp/src/inference-provider.ts index 413d2e24127a..1481862a8776 100644 --- a/extensions/llama-cpp/src/inference-provider.ts +++ b/extensions/llama-cpp/src/inference-provider.ts @@ -46,7 +46,7 @@ type LlamaCppInferenceRuntimeState = { llamaInstance?: Llama; operationQueue: Promise; lifecycle: "open" | "closing" | "closed"; - cleanupFailure?: { error: unknown }; + cleanupFailure?: { error: Error }; disposePromise?: Promise; }; @@ -86,6 +86,20 @@ function buildMessage(params: { }; } +function runtimeUnavailableMessage( + state: LlamaCppInferenceRuntimeState, + model: Parameters[0], +): AssistantMessage { + return buildMessage({ + model, + content: [], + stopReason: "error", + errorMessage: state.cleanupFailure + ? "llama.cpp runtime stopped after cleanup failed" + : "llama.cpp runtime is stopping", + }); +} + function extractText(content: unknown): string { if (typeof content === "string") { return content; @@ -259,7 +273,7 @@ async function disposeLoadedModel(state: LlamaCppInferenceRuntimeState): Promise } function recordCleanupFailure(state: LlamaCppInferenceRuntimeState, error: unknown): void { - state.cleanupFailure ??= { error }; + state.cleanupFailure ??= { error: error instanceof Error ? error : new Error(String(error)) }; state.lifecycle = "closed"; } @@ -358,14 +372,7 @@ function createLlamaCppStreamFnForRuntime( stream.push({ type: "error", reason: "error", - error: buildMessage({ - model, - content: [], - stopReason: "error", - errorMessage: state.cleanupFailure - ? "llama.cpp runtime stopped after cleanup failed" - : "llama.cpp runtime is stopping", - }), + error: runtimeUnavailableMessage(state, model), }); stream.end(); return stream; @@ -404,6 +411,14 @@ function createLlamaCppStreamFnForRuntime( started = true; signal?.removeEventListener("abort", abortWhileQueued); try { + if (state.lifecycle !== "open") { + stream.push({ + type: "error", + reason: "error", + error: runtimeUnavailableMessage(state, model), + }); + return; + } const runtime = await importNodeLlamaCpp(); const loaded = await getLoadedModel({ state,