mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(llama-cpp): make runtime cleanup best effort
This commit is contained in:
@@ -155,7 +155,7 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await inferenceRuntime.dispose();
|
||||
await inferenceRuntime.dispose().catch(() => undefined);
|
||||
});
|
||||
|
||||
describe("llama.cpp inference provider", () => {
|
||||
@@ -942,16 +942,20 @@ describe("llama.cpp inference provider", () => {
|
||||
expect(mocks.llamaDispose).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("retains failed cleanup ownership so disposal can be retried", async () => {
|
||||
it("keeps a failed best-effort cleanup as one disposal attempt", async () => {
|
||||
await collectTestEvents();
|
||||
mocks.contextDispose.mockRejectedValueOnce(new Error("context cleanup failed"));
|
||||
|
||||
await expect(inferenceRuntime.dispose()).rejects.toThrow("context cleanup failed");
|
||||
expect([mocks.modelDispose.mock.calls.length, mocks.llamaDispose.mock.calls.length]).toEqual([
|
||||
0, 0,
|
||||
]);
|
||||
await inferenceRuntime.dispose();
|
||||
expect(mocks.contextDispose).toHaveBeenCalledTimes(2);
|
||||
const firstDisposal = inferenceRuntime.dispose();
|
||||
await expect(firstDisposal).rejects.toThrow(
|
||||
"llama.cpp runtime cleanup failed: Error: context cleanup failed",
|
||||
);
|
||||
expect(mocks.modelDispose).toHaveBeenCalledOnce();
|
||||
expect(mocks.llamaDispose).toHaveBeenCalledOnce();
|
||||
const repeatedDisposal = inferenceRuntime.dispose();
|
||||
expect(repeatedDisposal).toBe(firstDisposal);
|
||||
await expect(repeatedDisposal).rejects.toThrow("context cleanup failed");
|
||||
expect(mocks.contextDispose).toHaveBeenCalledOnce();
|
||||
expect(mocks.modelDispose).toHaveBeenCalledOnce();
|
||||
expect(mocks.llamaDispose).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
@@ -305,33 +305,42 @@ async function serialize(
|
||||
}
|
||||
|
||||
function disposeLlamaCppInferenceRuntime(state: LlamaCppInferenceRuntimeState): Promise<void> {
|
||||
if (state.lifecycle === "closed") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (state.disposePromise) {
|
||||
return state.disposePromise;
|
||||
}
|
||||
state.lifecycle = "closing";
|
||||
const attempt = serialize(state, async () => {
|
||||
await disposeLoadedModel(state);
|
||||
// Plugin services stop once, and node-llama-cpp disposers mark themselves
|
||||
// disposed before awaiting cleanup. The first disposal attempt is authoritative.
|
||||
state.disposePromise = serialize(state, async () => {
|
||||
const errors: unknown[] = [];
|
||||
const attemptDispose = async (dispose: () => Promise<void>) => {
|
||||
try {
|
||||
await dispose();
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
};
|
||||
if (state.loadedModel) {
|
||||
const previous = state.loadedModel;
|
||||
await attemptDispose(() => previous.context.dispose());
|
||||
await attemptDispose(() => previous.model.dispose());
|
||||
if (state.loadedModel === previous) {
|
||||
state.loadedModel = undefined;
|
||||
}
|
||||
}
|
||||
if (state.llamaInstance) {
|
||||
const previous = state.llamaInstance;
|
||||
await previous.dispose();
|
||||
await attemptDispose(() => previous.dispose());
|
||||
if (state.llamaInstance === previous) {
|
||||
state.llamaInstance = undefined;
|
||||
}
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
throw new AggregateError(errors, `llama.cpp runtime cleanup failed: ${String(errors[0])}`);
|
||||
}
|
||||
}).finally(() => {
|
||||
state.lifecycle = "closed";
|
||||
});
|
||||
state.disposePromise = attempt.then(
|
||||
() => {
|
||||
state.lifecycle = "closed";
|
||||
state.disposePromise = undefined;
|
||||
},
|
||||
(error: unknown) => {
|
||||
state.disposePromise = undefined;
|
||||
throw error;
|
||||
},
|
||||
);
|
||||
return state.disposePromise;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user