mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 09:52:27 +00:00
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:
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)?.();
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user