mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
perf(native): run independent Mach-O slice commands concurrently (#2466)
inspect_macho launched otool, nm, two dyld_info reads, dwarfdump and vtool one at a time. Each launch also resolves the tool through `xcrun --find`, so a warm call spent about 350 ms in eight serial supervised launches. The slice commands only read the selected artifact, so launch them together. Every launch still settles before the result, and declared order still fixes provenance and which failure is reported. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4303dcdcf6
commit
2c7240cf9d
2 files changed
+78
-13
No files matched your search
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisOutputError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { err, ok } from "../domain/result.js";
|
||||
import type { NativeCommandCapture } from "./CommandRunner.js";
|
||||
import { inspectNativeMacho } from "./NativeMachoInspection.js";
|
||||
@@ -186,3 +189,64 @@ describe("Mach-O UUID reconciliation", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Mach-O slice command scheduling", () => {
|
||||
const SLICE_TOOLS = ["otool", "nm", "dyld_info", "dyld_info", "dwarfdump"];
|
||||
|
||||
/** Finish later-launched slice commands first and record overlap. */
|
||||
const reorderingRunner = (failing: ReadonlySet<string> = new Set()) => {
|
||||
const fixtures = new NativeFixtureRunner();
|
||||
let inFlight = 0;
|
||||
let maxInFlight = 0;
|
||||
let launched = 0;
|
||||
const run = async (tool: string, args: readonly string[]) => {
|
||||
if (tool === "file" || tool === "lipo") return fixtures.run(tool, args);
|
||||
const order = launched++;
|
||||
inFlight += 1;
|
||||
maxInFlight = Math.max(maxInFlight, inFlight);
|
||||
for (let turn = 0; turn < 12 - order * 2; turn += 1)
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
inFlight -= 1;
|
||||
if (failing.has(tool))
|
||||
return err(new AnalysisOutputError("inspect_macho", `${tool} failed`));
|
||||
return fixtures.run(tool, args);
|
||||
};
|
||||
return { run, maxInFlight: () => maxInFlight };
|
||||
};
|
||||
|
||||
const inspectWith = (run: ReturnType<typeof reorderingRunner>["run"]) =>
|
||||
inspectNativeMacho({
|
||||
target: nativeMachoTarget("/fixture/app"),
|
||||
run,
|
||||
invocation: (capture) => ({
|
||||
tool: capture.tool,
|
||||
command: [capture.executable, ...capture.arguments],
|
||||
tool_version: capture.toolVersion,
|
||||
version_reason: capture.versionReason,
|
||||
executable_sha256: capture.executableSha256,
|
||||
exit: { code: capture.exitCode, signal: capture.signal },
|
||||
stdout_bytes: capture.stdoutBytes,
|
||||
stderr_bytes: capture.stderrBytes,
|
||||
}),
|
||||
});
|
||||
|
||||
it("runs independent slice commands together and keeps provenance in declared order", async () => {
|
||||
const runner = reorderingRunner();
|
||||
const result = await inspectWith(runner.run);
|
||||
if (!result.ok) throw result.error;
|
||||
expect(runner.maxInFlight()).toBe(SLICE_TOOLS.length + 1);
|
||||
expect(result.value.result).toMatchObject({
|
||||
provenance: ["file", "lipo", ...SLICE_TOOLS, "vtool"].map((tool) => ({
|
||||
tool,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
it("reports the first failing slice command in declared order", async () => {
|
||||
const runner = reorderingRunner(new Set(["nm", "dwarfdump"]));
|
||||
const result = await inspectWith(runner.run);
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error).toMatchObject({ reason: "nm failed" });
|
||||
});
|
||||
});
|
||||
@@ -79,8 +79,11 @@ export const inspectNativeMacho = async (
|
||||
);
|
||||
const selectedArchitecture =
|
||||
selection.status === "selected" ? selection.architecture : null;
|
||||
for (const [tool, prefix] of REQUIRED_SLICE_COMMANDS) {
|
||||
const captured = await context.run(
|
||||
const runSliceCommand = ([tool, prefix]: readonly [
|
||||
string,
|
||||
readonly string[],
|
||||
]) =>
|
||||
context.run(
|
||||
tool,
|
||||
[
|
||||
...prefix,
|
||||
@@ -89,20 +92,18 @@ export const inspectNativeMacho = async (
|
||||
],
|
||||
context.signal,
|
||||
);
|
||||
// Slice commands only read the selected artifact, so launch them together.
|
||||
// Every launch settles before return; declared order fixes provenance and
|
||||
// which failure is reported.
|
||||
const [required, optional] = await Promise.all([
|
||||
Promise.all(REQUIRED_SLICE_COMMANDS.map(runSliceCommand)),
|
||||
runSliceCommand(OPTIONAL_VTOOL_COMMAND),
|
||||
]);
|
||||
for (const captured of required) {
|
||||
if (!captured.ok) return err(captured.error);
|
||||
captures.push(captured.value);
|
||||
}
|
||||
const limitations: string[] = [];
|
||||
const [tool, prefix] = OPTIONAL_VTOOL_COMMAND;
|
||||
const optional = await context.run(
|
||||
tool,
|
||||
[
|
||||
...prefix,
|
||||
...selectedArchitectureArguments(tool, selectedArchitecture),
|
||||
context.target.path,
|
||||
],
|
||||
context.signal,
|
||||
);
|
||||
if (optional.ok) captures.push(optional.value);
|
||||
else if (optional.error._tag === "AnalysisCapabilityUnavailableError")
|
||||
limitations.push(VTOOL_UNAVAILABLE_LIMITATION);
|
||||
|
||||
Reference in new issue
Block a user