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:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-12 21:12:05 -07:00
1 parent 4303dcdcf6
commit 2c7240cf9d
2 files changed
+78 -13

No files matched your search

+65 -1
View File
@@ -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" });
});
});
+13 -12
View File
@@ -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);