fix(kuna): resolve the function deadline before taking a run slot

An invalid REA_KUNA_TIMEOUT_MS throws once its runtime override is reset. That check now happens before either shared-slot acquisition, so a failed independent decompile cannot leave the provider-wide pool blocked.
This commit is contained in:
Cursor Agent authored and morluto committed 2026-11-11 20:16:24 +08:00
1 parent 5ba464527b
commit 0bf161f1bc
2 files changed
+105 -4

No files matched your search

@@ -0,0 +1,61 @@
import { setImmediate } from "node:timers/promises";
import { expect, it } from "vitest";
import { runWithAnalysisOperationSnapshot } from "../domain/analysisOperationSnapshot.js";
import type { KunaConfiguration } from "../kuna/KunaConfiguration.js";
import { KunaRunSlots } from "../kuna/KunaRunSlots.js";
import { reserveKunaFunctionRun } from "../kuna/KunaSessionClient.js";
const configuration = {
command: "/usr/bin/kuna",
cacheRoot: "/tmp/kuna-cache",
cacheMaxBytes: 4 * 1024 ** 3,
projectTimeoutMs: 30 * 60 * 1000,
projectTimeoutSource: "default",
projectTimeoutLaunchValid: true,
functionTimeoutMs: 120_000,
functionTimeoutSource: "default",
functionTimeoutLaunchValid: false,
jobs: 1,
} satisfies KunaConfiguration;
it("does not take a run slot when the function deadline is no longer valid", async () => {
const slots = new KunaRunSlots(1);
const signal = new AbortController().signal;
await expect(
reserveKunaFunctionRun(slots, signal, configuration),
).rejects.toThrow(
/REA_KUNA_TIMEOUT_MS is not a valid launch seed and no runtime override is active/,
);
const released = await slots.acquire(AbortSignal.timeout(50));
expect(released).toEqual(expect.any(Function));
released?.();
});
it("releases the function-run slot after the accepted deadline is finished", async () => {
const slots = new KunaRunSlots(1);
const signal = new AbortController().signal;
const reserved = await runWithAnalysisOperationSnapshot(
{
revision: 1,
overrides: { "kuna.function_timeout_ms": 5000 },
},
() => reserveKunaFunctionRun(slots, signal, configuration),
);
expect(reserved?.deadline).toEqual({
setting: "REA_KUNA_TIMEOUT_MS",
ms: 5000,
source: "runtime",
});
const next = slots.acquire(AbortSignal.timeout(50));
let granted = false;
void next.then((release) => {
granted = release !== undefined;
});
await setImmediate();
expect(granted).toBe(false);
reserved?.release();
expect(await next).toEqual(expect.any(Function));
(await next)?.();
});
+44 -4
View File
@@ -362,6 +362,24 @@ const procedureSelectorSchema = z.string().min(1);
* the background; assembly waits for it. Kuna always runs on a private copy
* whose digest matches the selected target.
*/
/**
* Resolve the function deadline before taking a shared run slot. A throw from
* an invalid launch seed must not leave the slot acquired.
*/
export const reserveKunaFunctionRun = async (
slots: KunaRunSlots,
signal: AbortSignal,
configuration: KunaConfiguration,
): Promise<
| { readonly deadline: KunaDeadline; readonly release: KunaRunRelease }
| undefined
> => {
const deadline = kunaFunctionDeadline(configuration);
const release = await slots.acquire(signal);
if (release === undefined) return undefined;
return { deadline, release };
};
export class KunaSessionClient implements AnalysisClient {
readonly #target: BinaryTarget;
readonly #profile: AnalysisProfileCommitment | undefined;
@@ -1604,9 +1622,31 @@ export class KunaSessionClient implements AnalysisClient {
): Promise<SingleFunctionOutcome> {
const prepared = await this.#commands.prepare();
if (!prepared.ok) return prepared;
const admitted = independent
? await this.#runSlots.acquire(signal)
: await this.#admitSingleFunction(signal);
if (independent) {
const reserved = await reserveKunaFunctionRun(
this.#runSlots,
signal,
installation.configuration,
);
if (reserved === undefined)
return err(
new AnalysisCancelledError(operation, { cause: signal.reason }),
);
const run = await this.#commands.run({
installation,
arguments: (input) => ["decompile", input, address, "--addr"],
operation,
deadline: reserved.deadline,
outputBytes: KUNA_FUNCTION_TEXT_BYTES,
signal,
releaseRun: reserved.release,
});
return run.ok ? ok(run.value.stdout.text.replace(/\n+$/u, "")) : run;
}
// The shared slot is taken only after the deadline is known. A throw from
// a reset override must not leave that slot acquired.
const deadline = kunaFunctionDeadline(installation.configuration);
const admitted = await this.#admitSingleFunction(signal);
if (admitted === EXPORT_READY) return admitted;
if (admitted === undefined)
return err(
@@ -1616,7 +1656,7 @@ export class KunaSessionClient implements AnalysisClient {
installation,
arguments: (input) => ["decompile", input, address, "--addr"],
operation,
deadline: kunaFunctionDeadline(installation.configuration),
deadline,
outputBytes: KUNA_FUNCTION_TEXT_BYTES,
signal,
releaseRun: admitted,