mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 09:52:27 +00:00
fix(settings): preserve regex cancellation and capability truth
This commit is contained in:
1 parent
40b6f1ab21
commit
e8908507c8
7 files changed
+134
-68
No files matched your search
@@ -3,7 +3,7 @@ import {
|
||||
currentAnalysisOperationSnapshot,
|
||||
currentAnalysisSettingOverride,
|
||||
} from "../domain/analysisOperationSnapshot.js";
|
||||
import { ConfigurationError } from "../domain/configurationErrors.js";
|
||||
import type { ConfigurationError } from "../domain/configurationErrors.js";
|
||||
import { ok, type Result } from "../domain/result.js";
|
||||
import type { AppConfig } from "./types.js";
|
||||
import {
|
||||
@@ -165,18 +165,6 @@ export const runtimeAnalysisConfig = (
|
||||
};
|
||||
};
|
||||
|
||||
/** Capability text may describe the built-in default. The operation still admits the seed. */
|
||||
export const capabilityLimit = <Value>(
|
||||
read: () => Value,
|
||||
fallback: Value,
|
||||
): Value => {
|
||||
try {
|
||||
return read();
|
||||
} catch (cause) {
|
||||
if (cause instanceof ConfigurationError) return fallback;
|
||||
throw cause;
|
||||
}
|
||||
};
|
||||
export const runtimeAsarHeaderAdmission = (
|
||||
environment: Readonly<Record<string, string | undefined>>,
|
||||
read?: AnalysisSettingsReader,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AnalysisSettings } from "../application/AnalysisSettings.js";
|
||||
import { runtimeAnalysisConfig } from "../config/runtimeAnalysisSettings.js";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { parseConfig } from "../config/parseConfig.js";
|
||||
@@ -409,3 +411,38 @@ describe("Hopper caller reconciliation failures", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("reports invalid regex configuration honestly without blocking capability discovery", () => {
|
||||
const environment = { REA_REGEX_MATCHING_DEADLINE_MS: "invalid" };
|
||||
const parsed = parseConfig(environment);
|
||||
if (!parsed.ok) throw parsed.error;
|
||||
const settings = new AnalysisSettings(environment);
|
||||
const provider = new HopperProvider(
|
||||
runtimeAnalysisConfig(parsed.value, settings.read),
|
||||
silentLogger,
|
||||
environment,
|
||||
);
|
||||
const limitation = () =>
|
||||
provider
|
||||
.capabilities()
|
||||
.find(({ operation }) => operation === "search_strings")
|
||||
?.limitations.join(" ");
|
||||
expect(limitation()).toContain("invalid launch configuration");
|
||||
expect(limitation()).not.toContain("5000 ms matching deadline");
|
||||
expect(
|
||||
settings.update({
|
||||
changes: [
|
||||
{ setting: "regex.matching_deadline_ms", action: "set", value: 3000 },
|
||||
],
|
||||
}).ok,
|
||||
).toBe(true);
|
||||
expect(settings.run(limitation)).toContain(
|
||||
"3000 ms matching deadline (source runtime)",
|
||||
);
|
||||
expect(
|
||||
settings.update({
|
||||
changes: [{ setting: "regex.matching_deadline_ms", action: "reset" }],
|
||||
}).ok,
|
||||
).toBe(true);
|
||||
expect(limitation()).toContain("invalid launch configuration");
|
||||
});
|
||||
@@ -49,9 +49,6 @@ import {
|
||||
reconciliationChildOptions,
|
||||
} from "./HopperCallerReconciliation.js";
|
||||
import { analysisClientFailureForOperation } from "../providers/AnalysisClientFailure.js";
|
||||
import { capabilityLimit } from "../config/runtimeAnalysisSettings.js";
|
||||
import { DEFAULT_REGEX_MATCHING_DEADLINE } from "../config/regexMatchingDeadline.js";
|
||||
import { DEFAULT_REGEX_WORKER_HEAP_REQUEST } from "../config/regexWorkerHeapRequest.js";
|
||||
|
||||
import { hopperCapabilities } from "./HopperProviderCapabilities.js";
|
||||
import { HOPPER_PROVIDER_IDENTITY } from "./HopperProviderMetadata.js";
|
||||
@@ -94,16 +91,8 @@ export class HopperProvider implements AnalysisProviderCandidate {
|
||||
|
||||
capabilities(): readonly CapabilityDescriptor[] {
|
||||
return hopperCapabilities(
|
||||
capabilityLimit(() => {
|
||||
const deadline = this.config.regexMatchingDeadline;
|
||||
void deadline.deadlineMs;
|
||||
return deadline;
|
||||
}, DEFAULT_REGEX_MATCHING_DEADLINE),
|
||||
capabilityLimit(() => {
|
||||
const request = this.config.regexWorkerHeapRequest;
|
||||
void request.heapMiB;
|
||||
return request;
|
||||
}, DEFAULT_REGEX_WORKER_HEAP_REQUEST),
|
||||
this.config.regexMatchingDeadline,
|
||||
this.config.regexWorkerHeapRequest,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+2
-13
@@ -27,9 +27,6 @@ import type {
|
||||
RetainedResourceScope,
|
||||
} from "../application/AnalysisProvider.js";
|
||||
import type { AppConfig } from "../config/types.js";
|
||||
import { DEFAULT_REGEX_MATCHING_DEADLINE } from "../config/regexMatchingDeadline.js";
|
||||
import { DEFAULT_REGEX_WORKER_HEAP_REQUEST } from "../config/regexWorkerHeapRequest.js";
|
||||
import { capabilityLimit } from "../config/runtimeAnalysisSettings.js";
|
||||
import type { ConfigurationError } from "../domain/configurationErrors.js";
|
||||
import { idaRegistrationDigest } from "./IdaRegistrationIdentity.js";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
@@ -434,16 +431,8 @@ export class IdaProvider implements AnalysisProviderCandidate {
|
||||
}
|
||||
#regexLimitation(): string {
|
||||
return launchRegexMatchingLimitation(
|
||||
capabilityLimit(() => {
|
||||
const deadline = this.config.regexMatchingDeadline;
|
||||
void deadline.deadlineMs;
|
||||
return deadline;
|
||||
}, DEFAULT_REGEX_MATCHING_DEADLINE),
|
||||
capabilityLimit(() => {
|
||||
const request = this.config.regexWorkerHeapRequest;
|
||||
void request.heapMiB;
|
||||
return request;
|
||||
}, DEFAULT_REGEX_WORKER_HEAP_REQUEST),
|
||||
this.config.regexMatchingDeadline,
|
||||
this.config.regexWorkerHeapRequest,
|
||||
);
|
||||
}
|
||||
#createBoundClient(
|
||||
|
||||
@@ -18,16 +18,13 @@ import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
|
||||
import { err, ok } from "../domain/result.js";
|
||||
import { RegularFileCleanupFailure } from "../filesystem/OwnedRegularFile.js";
|
||||
import {
|
||||
DEFAULT_REGEX_MATCHING_DEADLINE,
|
||||
resolveRegexMatchingDeadline,
|
||||
type RegexMatchingDeadline,
|
||||
} from "../config/regexMatchingDeadline.js";
|
||||
import {
|
||||
DEFAULT_REGEX_WORKER_HEAP_REQUEST,
|
||||
resolveRegexWorkerHeapRequest,
|
||||
type RegexWorkerHeapRequest,
|
||||
} from "../config/regexWorkerHeapRequest.js";
|
||||
import { capabilityLimit } from "../config/runtimeAnalysisSettings.js";
|
||||
import { snapshotEnvironment } from "../process/snapshotEnvironment.js";
|
||||
import {
|
||||
kunaFileCleanupError,
|
||||
@@ -119,14 +116,8 @@ export class KunaProvider implements AnalysisProviderCandidate {
|
||||
capabilities() {
|
||||
return kunaCapabilities(
|
||||
this.inspectAvailability(),
|
||||
capabilityLimit(() => {
|
||||
void this.#deadline.deadlineMs;
|
||||
return this.#deadline;
|
||||
}, DEFAULT_REGEX_MATCHING_DEADLINE),
|
||||
capabilityLimit(() => {
|
||||
void this.#heapRequest.heapMiB;
|
||||
return this.#heapRequest;
|
||||
}, DEFAULT_REGEX_WORKER_HEAP_REQUEST),
|
||||
this.#deadline,
|
||||
this.#heapRequest,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { AnalysisSettings } from "../application/AnalysisSettings.js";
|
||||
import { parseConfig } from "../config/parseConfig.js";
|
||||
import { runtimeAnalysisConfig } from "../config/runtimeAnalysisSettings.js";
|
||||
import { matchRegexValues } from "./RegexMatcher.js";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
@@ -481,3 +485,58 @@ it("keeps a selected heap request unless a positive Node flag overrides it", ()
|
||||
);
|
||||
expect(projected.details?.limit).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["regex.matching_deadline_ms", "REA_REGEX_MATCHING_DEADLINE_MS", 3000],
|
||||
["regex.worker_heap_request_mib", "REA_REGEX_WORKER_HEAP_REQUEST_MIB", 128],
|
||||
] as const)(
|
||||
"preserves pre-aborted cancellation before invalid %s and rejects the invalid seed until overridden",
|
||||
async (setting, environmentName, value) => {
|
||||
const environment = { [environmentName]: "invalid" };
|
||||
const parsed = parseConfig(environment);
|
||||
if (!parsed.ok) throw parsed.error;
|
||||
const settings = new AnalysisSettings(environment);
|
||||
const config = runtimeAnalysisConfig(parsed.value, settings.read);
|
||||
const input = {
|
||||
operation: "search_strings",
|
||||
pattern: "x",
|
||||
caseSensitive: true,
|
||||
values: ["x"],
|
||||
deadline: config.regexMatchingDeadline,
|
||||
heapRequest: config.regexWorkerHeapRequest,
|
||||
};
|
||||
const controller = new AbortController();
|
||||
const reason = { stopped: true };
|
||||
controller.abort(reason);
|
||||
const cancelled = await settings.run(() =>
|
||||
matchRegexValues({ ...input, signal: controller.signal }),
|
||||
);
|
||||
expect(cancelled).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisCancelledError" },
|
||||
});
|
||||
if (!cancelled.ok) expect(cancelled.error.cause).toBe(reason);
|
||||
const rejected = await settings.run(() => matchRegexValues(input));
|
||||
expect(rejected).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "ConfigurationError",
|
||||
settings: [expect.objectContaining({ setting: environmentName })],
|
||||
},
|
||||
});
|
||||
expect(
|
||||
settings.update({ changes: [{ setting, action: "set", value }] }).ok,
|
||||
).toBe(true);
|
||||
expect(await settings.run(() => matchRegexValues(input))).toEqual({
|
||||
ok: true,
|
||||
value: [0],
|
||||
});
|
||||
expect(
|
||||
settings.update({ changes: [{ setting, action: "reset" }] }).ok,
|
||||
).toBe(true);
|
||||
expect(await settings.run(() => matchRegexValues(input))).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "ConfigurationError" },
|
||||
});
|
||||
},
|
||||
);
|
||||
+31
-18
@@ -1,3 +1,4 @@
|
||||
import { ConfigurationError } from "../domain/configurationErrors.js";
|
||||
import { analysisSettingLimit } from "../domain/analysisSettingDefinitions.js";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Worker } from "node:worker_threads";
|
||||
@@ -441,12 +442,18 @@ export const regexMatchingLimitation = (
|
||||
export const launchRegexMatchingLimitation = (
|
||||
deadline: RegexMatchingDeadline = DEFAULT_REGEX_MATCHING_DEADLINE,
|
||||
request: RegexWorkerHeapRequest = DEFAULT_REGEX_WORKER_HEAP_REQUEST,
|
||||
): string =>
|
||||
regexMatchingLimitation(
|
||||
resolveLaunchRegexWorkerHeap(launchExecArgv, launchNodeOptions, request),
|
||||
deadline,
|
||||
request,
|
||||
);
|
||||
): string => {
|
||||
try {
|
||||
return regexMatchingLimitation(
|
||||
resolveLaunchRegexWorkerHeap(launchExecArgv, launchNodeOptions, request),
|
||||
deadline,
|
||||
request,
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
if (!(cause instanceof ConfigurationError)) throw cause;
|
||||
return "Regex mode is unavailable until its invalid launch configuration is corrected with update_analysis_settings or by restarting REA. Inspect analysis settings for the affected value.";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Capability text for the default deadline on this process's launch heap.
|
||||
@@ -583,24 +590,30 @@ export const matchRegexValues = async (input: {
|
||||
/** Resolved heap request. Omitted means the 128 MiB default, not ambient `process.env`. */
|
||||
readonly heapRequest?: RegexWorkerHeapRequest | undefined;
|
||||
}): Promise<Result<readonly number[], AnalysisError>> => {
|
||||
const selectedDeadline = input.deadline ?? DEFAULT_REGEX_MATCHING_DEADLINE;
|
||||
const selectedHeap = input.heapRequest ?? DEFAULT_REGEX_WORKER_HEAP_REQUEST;
|
||||
// Copy the values this match accepted. A later override reset must not
|
||||
// rewrite the deadline or heap reported if the match fails.
|
||||
const deadline: RegexMatchingDeadline = {
|
||||
deadlineMs: selectedDeadline.deadlineMs,
|
||||
source: selectedDeadline.source,
|
||||
};
|
||||
const heapRequest: RegexWorkerHeapRequest = {
|
||||
heapMiB: selectedHeap.heapMiB,
|
||||
source: selectedHeap.source,
|
||||
};
|
||||
if (input.signal?.aborted)
|
||||
return err(
|
||||
new AnalysisCancelledError(input.operation, {
|
||||
cause: input.signal.reason,
|
||||
}),
|
||||
);
|
||||
const selectedDeadline = input.deadline ?? DEFAULT_REGEX_MATCHING_DEADLINE;
|
||||
const selectedHeap = input.heapRequest ?? DEFAULT_REGEX_WORKER_HEAP_REQUEST;
|
||||
let deadline: RegexMatchingDeadline;
|
||||
let heapRequest: RegexWorkerHeapRequest;
|
||||
try {
|
||||
// Capture the accepted values before work begins; subsequent updates do not alter this match.
|
||||
deadline = {
|
||||
deadlineMs: selectedDeadline.deadlineMs,
|
||||
source: selectedDeadline.source,
|
||||
};
|
||||
heapRequest = {
|
||||
heapMiB: selectedHeap.heapMiB,
|
||||
source: selectedHeap.source,
|
||||
};
|
||||
} catch (cause: unknown) {
|
||||
if (cause instanceof ConfigurationError) return err(cause);
|
||||
throw cause;
|
||||
}
|
||||
let worker: Worker;
|
||||
try {
|
||||
worker = new Worker(WORKER_SOURCE, {
|
||||
|
||||
Reference in new issue
Block a user