From 125de0bdc4f3f41e7b854bc57fa594fdd678cfbc Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 11 Nov 2026 05:22:56 -0500 Subject: [PATCH] fix(kuna): retain the deadline owned by project export --- src/kuna/KunaProjectExport.ts | 5 +- src/kuna/KunaSessionClient.ts | 36 +---------- .../providers/kuna/kunaProvider.test.ts | 59 +++++++++++++++++++ 3 files changed, 64 insertions(+), 36 deletions(-) diff --git a/src/kuna/KunaProjectExport.ts b/src/kuna/KunaProjectExport.ts index c4cb079d51..410d9b26b1 100644 --- a/src/kuna/KunaProjectExport.ts +++ b/src/kuna/KunaProjectExport.ts @@ -215,7 +215,8 @@ export const exportKunaProject = async ( ): Promise => { 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, diff --git a/src/kuna/KunaSessionClient.ts b/src/kuna/KunaSessionClient.ts index a88ac23d14..50ebcd580e 100644 --- a/src/kuna/KunaSessionClient.ts +++ b/src/kuna/KunaSessionClient.ts @@ -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> | undefined; - /** Launch configuration captured when the export starts, for its deadline source. */ - #kunaConfiguration: KunaConfiguration | undefined; #inventory: | Promise> | 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 { - 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); diff --git a/tests/boundary/providers/kuna/kunaProvider.test.ts b/tests/boundary/providers/kuna/kunaProvider.test.ts index 67a178daf6..6a0366d63e 100644 --- a/tests/boundary/providers/kuna/kunaProvider.test.ts +++ b/tests/boundary/providers/kuna/kunaProvider.test.ts @@ -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,