fix(kuna): retain the deadline owned by project export

This commit is contained in:
morluto committed 2026-11-11 20:16:24 +08:00
1 parent b3dcddc115
commit 125de0bdc4
3 files changed
+64 -36

No files matched your search

+3 -2
View File
@@ -215,7 +215,8 @@ export const exportKunaProject = async (
): Promise<KunaExportOutcome> => {
const { installation, signal } = request;
const { configuration } = installation;
const deadline = Date.now() + kunaProjectDeadline(configuration).ms;
const projectDeadline = kunaProjectDeadline(configuration);
const deadline = Date.now() + projectDeadline.ms;
const input = join(request.workspace, "input", basename(request.targetPath));
await mkdir(join(request.workspace, "input"), { mode: 0o700 });
await mkdir(request.output, { recursive: true, mode: 0o700 });
@@ -240,7 +241,7 @@ export const exportKunaProject = async (
workspace: request.workspace,
operation: KUNA_EXPORT_OPERATION,
timeoutMs: deadline - Date.now(),
deadline: kunaProjectDeadline(configuration),
deadline: projectDeadline,
outputBytes,
signal,
launcher: request.launcher,
+2 -34
View File
@@ -39,7 +39,6 @@ import { combineFunctionRelationshipCoverage } from "../domain/functionCallCover
import { functionDossierSchema } from "../domain/native/functionDossier.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import type { JsonValue } from "../domain/jsonValue.js";
import { analysisSettingLimit } from "../domain/analysisSettingDefinitions.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import type { RelationshipCoverage } from "../domain/relationshipCoverage.js";
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
@@ -292,7 +291,6 @@ const exportUnavailableMessage = (
const exportUnavailable = (
operation: string,
failure: AnalysisError,
deadline?: KunaDeadline,
): AnalysisError => {
if (failure instanceof AnalysisCancelledError || failure.cleanupIncomplete)
return failure;
@@ -300,20 +298,6 @@ const exportUnavailable = (
const exported = projectAnalysisError(failure);
const restart =
"REA reads Kuna settings when it starts, so restart REA, then reopen the target.";
if (
failure instanceof AnalysisTimeoutError &&
failure.limit === undefined &&
deadline !== undefined &&
deadline.setting === "REA_KUNA_PROJECT_TIMEOUT_MS" &&
deadline.ms === failure.timeoutMs
)
failure.bindLimit(
analysisSettingLimit(
"kuna.project_timeout_ms",
deadline.ms,
deadline.source,
),
);
const next =
failure instanceof AnalysisTimeoutError
? `To retry, set kuna.project_timeout_ms above ${String(failure.timeoutMs)} ms with update_analysis_settings. It can be changed at runtime. The launch seed is REA_KUNA_PROJECT_TIMEOUT_MS. Then reopen the target so the export runs again.`
@@ -416,8 +400,6 @@ export class KunaSessionClient implements AnalysisClient {
#installation:
| SharedStartupAttempt<Result<KunaInstallation, AnalysisError>>
| undefined;
/** Launch configuration captured when the export starts, for its deadline source. */
#kunaConfiguration: KunaConfiguration | undefined;
#inventory:
| Promise<Result<readonly KunaInventoryFunction[], AnalysisError>>
| undefined;
@@ -2060,14 +2042,7 @@ export class KunaSessionClient implements AnalysisClient {
return this.#starting;
}
#projectDeadline(): KunaDeadline | undefined {
return this.#kunaConfiguration === undefined
? undefined
: kunaProjectDeadline(this.#kunaConfiguration);
}
async #lookupOrBuild(installation: KunaInstallation): Promise<void> {
this.#kunaConfiguration = installation.configuration;
const key = kunaProjectKey({
targetSha256: this.#target.sha256,
targetBasename: basename(this.#target.path),
@@ -2305,11 +2280,7 @@ export class KunaSessionClient implements AnalysisClient {
settled.error instanceof KunaPublishedProjectCleanupError
)
return undefined;
const unavailable = exportUnavailable(
operation,
settled.error,
this.#projectDeadline(),
);
const unavailable = exportUnavailable(operation, settled.error);
return unavailable instanceof AnalysisCapabilityUnavailableError
? unavailable
: undefined;
@@ -2343,10 +2314,7 @@ export class KunaSessionClient implements AnalysisClient {
}, WAIT_PROGRESS_INTERVAL_MS);
try {
const built = await waitFor(project, options.signal, operation);
if (!built.ok)
return err(
exportUnavailable(operation, built.error, this.#projectDeadline()),
);
if (!built.ok) return err(exportUnavailable(operation, built.error));
return ok(this.#readerFor(built.value));
} finally {
clearInterval(timer);
@@ -20,6 +20,7 @@ import { OwnedFileHandle } from "../../../../src/filesystem/OwnedFileHandle.js";
import { AnalysisProviderRegistry } from "../../../../src/application/binary/AnalysisProviderRegistry.js";
import { composeBinarySession } from "../../../../src/application/binary/BinarySessionComposition.js";
import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js";
import { runWithAnalysisOperationSnapshot } from "../../../../src/domain/analysisOperationSnapshot.js";
import { KUNA_VALUE_FLOW_UNAVAILABLE } from "../../../../src/kuna/KunaProviderCapabilities.js";
import { KunaProvider } from "../../../../src/kuna/KunaProvider.js";
import { KUNA_FUNCTION_TEXT_BYTES } from "../../../../src/kuna/KunaLimits.js";
@@ -1154,6 +1155,64 @@ it.each([
},
);
it("retains the producing project deadline after its runtime override is reset", async () => {
const { client, first } = await runWithAnalysisOperationSnapshot(
{ revision: 1, overrides: { "kuna.project_timeout_ms": 300 } },
async () => {
const { open } = await session(
"rea-kuna-retained-deadline-",
{ project: "hang" },
{ REA_KUNA_PROJECT_TIMEOUT_MS: "invalid" },
);
const client = open();
const first = await client.execute("procedure_assembly", {
procedure: "main",
});
return { client, first };
},
);
try {
const later = await runWithAnalysisOperationSnapshot(
{ revision: 2, overrides: {} },
() => client.execute("analyze_function", { procedure: "main" }),
);
for (const result of [first, later]) {
expect(result.ok).toBe(false);
if (result.ok) continue;
expect(result.error).toMatchObject({
_tag: "AnalysisCapabilityUnavailableError",
cause: {
_tag: "AnalysisTimeoutError",
timeoutMs: 300,
limit: {
setting: "kuna.project_timeout_ms",
effective_value: 300,
source: "runtime",
},
},
});
}
const fallback = await runWithAnalysisOperationSnapshot(
{ revision: 2, overrides: {} },
() => client.execute("procedure_pseudo_code", { procedure: "main" }),
);
expect(fallback).toMatchObject({
ok: true,
value: {
siblingUnavailability: {
cause: {
_tag: "AnalysisTimeoutError",
timeoutMs: 300,
limit: { setting: "kuna.project_timeout_ms", source: "runtime" },
},
},
},
});
} finally {
expect(await client.close()).toEqual({ ok: true, value: null });
}
});
it("lets one caller stop waiting for the shared inventory without failing another", async () => {
const { open } = await session("rea-kuna-shared-inventory-", {
inventoryDelayMs: 700,