mirror of
https://github.com/morluto/rea.git
synced 2026-10-30 19:35:56 +00:00
fix(javascript): retain static facts under projection pressure (#7986)
* fix(javascript): retain static facts under projection pressure * fix(javascript): scope IPC ambiguity to omitted projections * fix(javascript): preserve IPC uncertainty for omitted sources * fix(javascript): qualify IPC pairing by wrapper coverage * fix(javascript): retain provisional IPC wrapper matches
This commit is contained in:
1 parent
f60e42a9ae
commit
54f5cf7e14
13 files changed
+1174
-40
No files matched your search
@@ -21,6 +21,7 @@ import type {
|
||||
JavaScriptAnalyzedSource,
|
||||
JavaScriptSourceAnalysisFailure,
|
||||
JavaScriptSourceAnalysisLimits,
|
||||
JavaScriptRevokedStaticProjection,
|
||||
} from "../../domain/javascript/javascriptSourceAnalysis.js";
|
||||
import { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
@@ -119,6 +120,13 @@ export class JavaScriptAnalysisWorker {
|
||||
#phase = "startup";
|
||||
#lastFailure: AnalysisError | undefined;
|
||||
#estimatedRetainedBytes = 0;
|
||||
#staticProjectionReservations: {
|
||||
sourceIndex: number;
|
||||
path: string;
|
||||
bytes: number;
|
||||
}[] = [];
|
||||
#revokedStaticProjections: JavaScriptRevokedStaticProjection[] = [];
|
||||
#nextSourceIndex = 0;
|
||||
#applicationProjectionFailure: AnalysisResourceConstraintError | undefined;
|
||||
#parentCapacityExhausted = false;
|
||||
#sourcesRemaining: number | undefined;
|
||||
@@ -230,6 +238,9 @@ export class JavaScriptAnalysisWorker {
|
||||
);
|
||||
this.#sourcesRemaining = sourceCount;
|
||||
this.#estimatedRetainedBytes = 0;
|
||||
this.#staticProjectionReservations = [];
|
||||
this.#revokedStaticProjections = [];
|
||||
this.#nextSourceIndex = 0;
|
||||
this.#parentCapacityExhausted = false;
|
||||
this.#applicationProjectionFailure = undefined;
|
||||
this.#refreshResultBudget();
|
||||
@@ -270,8 +281,10 @@ export class JavaScriptAnalysisWorker {
|
||||
let module: JavaScriptModuleSemanticIr | null = null;
|
||||
let projection: JavaScriptSemanticFileProjection | null = null;
|
||||
const id = randomUUID();
|
||||
const sourceIndex = this.#nextSourceIndex++;
|
||||
this.#applicationProjectionFailure = undefined;
|
||||
this.#parentCapacityExhausted = false;
|
||||
this.#revokedStaticProjections = [];
|
||||
if (this.#sourcesRemaining !== undefined)
|
||||
this.#sourcesRemaining = Math.max(0, this.#sourcesRemaining - 1);
|
||||
let deadline: ProviderStartupDeadline | undefined;
|
||||
@@ -358,7 +371,12 @@ export class JavaScriptAnalysisWorker {
|
||||
OPERATION,
|
||||
"JavaScript worker emitted a duplicate static checkpoint",
|
||||
);
|
||||
javascript = await this.#readCheckpoint(id, file.path, response);
|
||||
javascript = await this.#readCheckpoint(
|
||||
id,
|
||||
file.path,
|
||||
sourceIndex,
|
||||
response,
|
||||
);
|
||||
if (this.#parentCapacityExhausted)
|
||||
throw new AnalysisResourceConstraintError(
|
||||
OPERATION,
|
||||
@@ -519,7 +537,7 @@ export class JavaScriptAnalysisWorker {
|
||||
this.#runtime.rootPath !== undefined
|
||||
) {
|
||||
try {
|
||||
javascript = await this.#readCheckpoint(id, file.path);
|
||||
javascript = await this.#readCheckpoint(id, file.path, sourceIndex);
|
||||
} catch (checkpointCause: unknown) {
|
||||
void checkpointCause;
|
||||
}
|
||||
@@ -573,15 +591,34 @@ export class JavaScriptAnalysisWorker {
|
||||
if (result.ok) this.#lastFailure = error;
|
||||
result = err({ error, javascript, module, projection });
|
||||
}
|
||||
if (this.#applicationProjectionFailure !== undefined)
|
||||
if (
|
||||
this.#applicationProjectionFailure !== undefined ||
|
||||
this.#revokedStaticProjections.length > 0
|
||||
)
|
||||
return result.ok
|
||||
? ok({
|
||||
...result.value,
|
||||
applicationProjectionFailure: this.#applicationProjectionFailure,
|
||||
...(this.#applicationProjectionFailure === undefined
|
||||
? {}
|
||||
: {
|
||||
applicationProjectionFailure:
|
||||
this.#applicationProjectionFailure,
|
||||
}),
|
||||
...(this.#revokedStaticProjections.length === 0
|
||||
? {}
|
||||
: { revokedStaticProjections: this.#revokedStaticProjections }),
|
||||
})
|
||||
: err({
|
||||
...result.error,
|
||||
applicationProjectionFailure: this.#applicationProjectionFailure,
|
||||
...(this.#applicationProjectionFailure === undefined
|
||||
? {}
|
||||
: {
|
||||
applicationProjectionFailure:
|
||||
this.#applicationProjectionFailure,
|
||||
}),
|
||||
...(this.#revokedStaticProjections.length === 0
|
||||
? {}
|
||||
: { revokedStaticProjections: this.#revokedStaticProjections }),
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -709,6 +746,7 @@ export class JavaScriptAnalysisWorker {
|
||||
async #readCheckpoint(
|
||||
id: string,
|
||||
sourcePath: string,
|
||||
sourceIndex: number,
|
||||
expected?: { readonly bytes: number; readonly sha256: string },
|
||||
): Promise<JavaScriptStaticAnalysis> {
|
||||
const rootPath = this.#runtime.rootPath;
|
||||
@@ -746,8 +784,52 @@ export class JavaScriptAnalysisWorker {
|
||||
const decoded = safeParseUtf8Json(checkpoint);
|
||||
if (!decoded.ok) throw decoded.cause;
|
||||
const facts = javaScriptWorkerStaticAnalysisSchema.parse(decoded.value);
|
||||
const remaining = this.#remainingTotalResultBudget();
|
||||
const retainedBytes = bytes * STATIC_CHECKPOINT_RETENTION_EXPANSION;
|
||||
// Application relationships are materialized only after all source files
|
||||
// have been examined. Reclaim their pending reservations to keep room for
|
||||
// later static observations, without relaxing the actual retained bound.
|
||||
const needed =
|
||||
retainedBytes - (this.#resultBudgetBytes - this.#estimatedRetainedBytes);
|
||||
const liveAvailable =
|
||||
this.#remainingParentHeapBytes() -
|
||||
24 * JAVASCRIPT_ANALYSIS_TRANSFER_RECORD_BYTES;
|
||||
if (needed > 0 && retainedBytes <= liveAvailable) {
|
||||
// Plan all revocations first so an insufficient set leaves the earlier
|
||||
// relationships intact rather than sacrificing them for a failed read.
|
||||
let start = this.#staticProjectionReservations.length;
|
||||
let reclaimable = 0;
|
||||
while (start > 0 && reclaimable < needed) {
|
||||
start--;
|
||||
reclaimable += this.#staticProjectionReservations[start]!.bytes;
|
||||
}
|
||||
if (reclaimable >= needed) {
|
||||
const revoked = this.#staticProjectionReservations.splice(start);
|
||||
for (const reservation of revoked) {
|
||||
this.#estimatedRetainedBytes -= reservation.bytes;
|
||||
this.#revokedStaticProjections.push({
|
||||
sourceIndex: reservation.sourceIndex,
|
||||
error: new AnalysisResourceConstraintError(
|
||||
OPERATION,
|
||||
"memory",
|
||||
`Expanded application relationships for ${reservation.path} were omitted to retain static facts for later sources`,
|
||||
{ parent_result_budget_bytes: this.#resultBudgetBytes },
|
||||
{
|
||||
remediationAction: `Read the retained static facts or analyze fewer source files. ${JAVASCRIPT_PARENT_RESULT_BUDGET_REMEDIATION}`,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use the same live-heap observation for the reclamation decision and
|
||||
// admission so a second read cannot reject after revocations commit.
|
||||
const remaining = Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
this.#resultBudgetBytes - this.#estimatedRetainedBytes,
|
||||
liveAvailable,
|
||||
),
|
||||
);
|
||||
this.#parentCapacityExhausted = retainedBytes > remaining;
|
||||
this.#estimatedRetainedBytes += retainedBytes;
|
||||
const expandedBytes = estimateJavaScriptStaticApplicationProjection(
|
||||
@@ -756,9 +838,14 @@ export class JavaScriptAnalysisWorker {
|
||||
sourcePath,
|
||||
);
|
||||
const availableResultBudget = this.#remainingResultBudget();
|
||||
if (expandedBytes <= availableResultBudget)
|
||||
if (expandedBytes <= availableResultBudget) {
|
||||
this.#estimatedRetainedBytes += expandedBytes;
|
||||
else
|
||||
this.#staticProjectionReservations.push({
|
||||
sourceIndex,
|
||||
path: sourcePath,
|
||||
bytes: expandedBytes,
|
||||
});
|
||||
} else
|
||||
this.#applicationProjectionFailure = new AnalysisResourceConstraintError(
|
||||
OPERATION,
|
||||
"memory",
|
||||
|
||||
Reference in new issue
Block a user