mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(kuna): verify a warm export once for concurrent first requests (#4049)
Every request a session admitted checked `#project` before awaiting the cache lookup, so concurrent first requests, such as batch_decompile's parallel resolutions on a fresh server, each re-hashed every export file and re-parsed the export. Concurrent first requests now share one lookup. Refs #1957 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1b8a9c831b
commit
b6529bf1af
3 files changed
+46
-4
No files matched your search
@@ -40,7 +40,7 @@ Cross-references, callers, callees and basic blocks are not available from this
|
||||
|
||||
The first analysis request in a session looks for this target's export in the cache. `list_documents` reads the selected path without initializing analysis. The export is keyed by the target's SHA-256, the Kuna executable's SHA-256, its version and `--jobs`.
|
||||
|
||||
- **Warm cache:** analysis reads reuse the verified export. A fresh server verifies its file digests; that cost scales with export size.
|
||||
- **Warm cache:** analysis reads reuse the verified export. A fresh server verifies its file digests once, even for concurrent first requests; that cost scales with export size.
|
||||
- **Cold cache:**
|
||||
- REA starts `kuna decompile-project` in the background on a private copy of the target.
|
||||
- Meanwhile, pseudocode comes from one `kuna decompile --addr` run, and listings from `kuna functions --json`.
|
||||
|
||||
@@ -193,6 +193,8 @@ export class KunaSessionClient implements AnalysisClient {
|
||||
| Promise<Result<readonly KunaInventoryFunction[], AnalysisError>>
|
||||
| undefined;
|
||||
#project: Promise<Result<KunaProject, AnalysisError>> | undefined;
|
||||
/** The cache lookup that decides #project, shared by concurrent first requests. */
|
||||
#starting: Promise<void> | undefined;
|
||||
#reader: KunaProjectReader | undefined;
|
||||
#inventoryIndex: InventoryIndex | undefined;
|
||||
#status: KunaExportStatus | undefined;
|
||||
@@ -998,8 +1000,17 @@ export class KunaSessionClient implements AnalysisClient {
|
||||
}
|
||||
|
||||
/** Reuse a verified export, or start building one in the background. */
|
||||
async #startProject(installation: KunaInstallation): Promise<void> {
|
||||
if (this.#project !== undefined) return;
|
||||
#startProject(installation: KunaInstallation): Promise<void> {
|
||||
if (this.#project !== undefined) return Promise.resolve();
|
||||
// Concurrent first requests share one lookup, which verifies every
|
||||
// export file's digest on a fresh server.
|
||||
this.#starting ??= this.#lookupOrBuild(installation).finally(() => {
|
||||
this.#starting = undefined;
|
||||
});
|
||||
return this.#starting;
|
||||
}
|
||||
|
||||
async #lookupOrBuild(installation: KunaInstallation): Promise<void> {
|
||||
const key = kunaProjectKey({
|
||||
targetSha256: this.#target.sha256,
|
||||
kunaSha256: installation.sha256,
|
||||
|
||||
@@ -10,13 +10,14 @@ import {
|
||||
} from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { expect, it as test } from "vitest";
|
||||
import { expect, it as test, vi } from "vitest";
|
||||
|
||||
import type { BinaryTarget } from "../../../../src/domain/binaryTargetTypes.js";
|
||||
import type { JsonValue } from "../../../../src/domain/jsonValue.js";
|
||||
import type { KunaProcessLauncher } from "../../../../src/kuna/KunaCommand.js";
|
||||
import { KunaProvider } from "../../../../src/kuna/KunaProvider.js";
|
||||
import { KUNA_FUNCTION_TEXT_BYTES } from "../../../../src/kuna/KunaLimits.js";
|
||||
import { KunaProjectCache } from "../../../../src/kuna/KunaProjectCache.js";
|
||||
import { cleanupOwnedProcessGroup } from "../../../../src/process/ProcessOwnership.js";
|
||||
import { spawnOwnedProviderProcess } from "../../../../src/process/ProviderProcess.js";
|
||||
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
|
||||
@@ -186,6 +187,36 @@ it("answers a cold request from one function, then serves the finished export",
|
||||
await again.close();
|
||||
});
|
||||
|
||||
it("verifies a warm export once for concurrent first requests on a fresh server", async () => {
|
||||
const { kuna, target, cacheRoot, profile, open } = await session(
|
||||
"rea-kuna-warm-concurrent-",
|
||||
);
|
||||
const builder = open();
|
||||
await run(builder, "procedure_assembly", { procedure: "main" });
|
||||
expect(await builder.close()).toEqual({ ok: true, value: null });
|
||||
|
||||
const lookup = vi.spyOn(KunaProjectCache.prototype, "lookup");
|
||||
const fresh = new KunaProvider(
|
||||
{ REA_KUNA_COMMAND: kuna, REA_KUNA_CACHE_DIR: cacheRoot },
|
||||
{ platform: process.platform },
|
||||
).createClient(target, profile);
|
||||
try {
|
||||
const answers = await Promise.all(
|
||||
["main", "parse", "0x1000", "main"].map((procedure) =>
|
||||
run(fresh, "procedure_pseudo_code", { procedure }),
|
||||
),
|
||||
);
|
||||
for (const answer of answers)
|
||||
expect(answer.rawResult).toMatchObject({
|
||||
source: { kind: "project-export" },
|
||||
});
|
||||
expect(lookup).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
lookup.mockRestore();
|
||||
expect(await fresh.close()).toEqual({ ok: true, value: null });
|
||||
}
|
||||
});
|
||||
|
||||
it("lists, searches and resolves procedures from Kuna's inventory", async () => {
|
||||
const { open } = await session("rea-kuna-inventory-");
|
||||
const client = open();
|
||||
|
||||
Reference in new issue
Block a user