mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 09:52:27 +00:00
fix(frida): retain cancellation through final cleanup (#9963)
fix(frida): retain caller cancellation through failed session cleanup
This commit is contained in:
1 parent
fcf8b99a77
commit
2bb266b39b
2 files changed
+91
-24
No files matched your search
@@ -68,6 +68,49 @@ const createProvider = (
|
||||
...overrides,
|
||||
});
|
||||
|
||||
it.each(["startSession", "instrument"] as const)(
|
||||
"retains cancellation beside a physical failed %s handshake",
|
||||
async (method) => {
|
||||
const controller = new AbortController();
|
||||
const reason = { caller: "cancelled during failed start" };
|
||||
const primary = new AnalysisCapabilityUnavailableError(
|
||||
"frida",
|
||||
"start_frida_session",
|
||||
"native attach failed",
|
||||
);
|
||||
let closes = 0;
|
||||
const service = new FridaInstrumentationService(
|
||||
createProvider({
|
||||
async startSession() {
|
||||
controller.abort(reason);
|
||||
return { ok: false, error: primary };
|
||||
},
|
||||
async closeSession() {
|
||||
closes++;
|
||||
return { ok: true, value: { targetResumeObserved: false } };
|
||||
},
|
||||
}),
|
||||
);
|
||||
const input = { mode: "attach" as const, deviceId: "local", pid: 10 };
|
||||
const result =
|
||||
method === "startSession"
|
||||
? await service.startSession(input, controller.signal)
|
||||
: await service.instrument(
|
||||
{
|
||||
...input,
|
||||
source: { sourceKind: "inline", source: "unused" },
|
||||
durationMs: 0,
|
||||
},
|
||||
controller.signal,
|
||||
);
|
||||
if (result.ok) throw new Error("Expected failed start");
|
||||
expect(result.error).toBe(primary);
|
||||
expect(primary.secondaryFailures).toHaveLength(1);
|
||||
expect(primary.secondaryFailures[0]?.cause).toBe(reason);
|
||||
expect(closes).toBe(0);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps default Frida status identity and messages and refreshes every summary", () => {
|
||||
let reads = 0;
|
||||
const provider = createProvider({
|
||||
@@ -514,6 +557,8 @@ it.each([false, true])(
|
||||
} else {
|
||||
expect(result.error).toBe(primary);
|
||||
}
|
||||
expect(primary.secondaryFailures).toHaveLength(1);
|
||||
expect(primary.secondaryFailures[0]?.cause).toBe(abortReason);
|
||||
expect(closes).toBe(1);
|
||||
},
|
||||
);
|
||||
@@ -1162,6 +1207,8 @@ it.each([
|
||||
} else {
|
||||
expect(result.error).toBe(primary);
|
||||
}
|
||||
expect(primary.secondaryFailures).toHaveLength(1);
|
||||
expect(primary.secondaryFailures[0]?.cause).toBe(abortReason);
|
||||
expect(closes).toBe(1);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type AnalysisError,
|
||||
} from "../../domain/analysisErrorBase.js";
|
||||
import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js";
|
||||
import { analysisErrorWithCancellation } from "../../domain/analysisErrorCancellation.js";
|
||||
import {
|
||||
AnalysisAccessDeniedError,
|
||||
AnalysisCancelledError,
|
||||
@@ -165,6 +166,13 @@ export class FridaInstrumentationService {
|
||||
),
|
||||
);
|
||||
const started = await this.#provider.startSession(input);
|
||||
if (!started.ok && signal?.aborted)
|
||||
return err(
|
||||
analysisErrorWithCancellation(
|
||||
started.error,
|
||||
new AnalysisCancelledError(operation, { cause: signal.reason }),
|
||||
),
|
||||
);
|
||||
if (!started.ok || !signal?.aborted) return started;
|
||||
return this.#cancelSession(operation, started.value, input, signal.reason);
|
||||
}
|
||||
@@ -323,18 +331,12 @@ export class FridaInstrumentationService {
|
||||
? loaded.error
|
||||
: undefined,
|
||||
);
|
||||
if (!loaded.ok) {
|
||||
const closed = await this.#provider.closeSession(started.value.sessionId);
|
||||
return closed.ok
|
||||
? err(resolvedAnalysisError(loaded.error))
|
||||
: err(
|
||||
analysisErrorWithCleanupFailure(
|
||||
loaded.error,
|
||||
closed.error,
|
||||
"instrument_with_frida",
|
||||
).bindCleanupDependencies([loaded.error, closed.error]),
|
||||
);
|
||||
}
|
||||
if (!loaded.ok)
|
||||
return this.#closeFailedInstrumentation(
|
||||
started.value.sessionId,
|
||||
loaded.error,
|
||||
signal,
|
||||
);
|
||||
if (signal?.aborted)
|
||||
return this.#cancelSession(
|
||||
"instrument_with_frida",
|
||||
@@ -351,20 +353,12 @@ export class FridaInstrumentationService {
|
||||
if (
|
||||
!resumed.ok &&
|
||||
!(signal?.aborted && resumed.error instanceof AnalysisCancelledError)
|
||||
) {
|
||||
const closed = await this.#provider.closeSession(
|
||||
)
|
||||
return this.#closeFailedInstrumentation(
|
||||
started.value.sessionId,
|
||||
resumed.error,
|
||||
signal,
|
||||
);
|
||||
return closed.ok
|
||||
? resumed
|
||||
: err(
|
||||
analysisErrorWithCleanupFailure(
|
||||
resumed.error,
|
||||
closed.error,
|
||||
"instrument_with_frida",
|
||||
).bindCleanupDependencies([resumed.error, closed.error]),
|
||||
);
|
||||
}
|
||||
if (signal?.aborted)
|
||||
return this.#cancelSession(
|
||||
"instrument_with_frida",
|
||||
@@ -431,6 +425,32 @@ export class FridaInstrumentationService {
|
||||
});
|
||||
}
|
||||
|
||||
/** Release the failed experiment while retaining its physical and caller outcomes. */
|
||||
async #closeFailedInstrumentation(
|
||||
sessionId: string,
|
||||
primary: AnalysisError,
|
||||
signal: AbortSignal | undefined,
|
||||
): Promise<Result<never, AnalysisError>> {
|
||||
const closed = await this.#provider.closeSession(sessionId);
|
||||
const failure = closed.ok
|
||||
? resolvedAnalysisError(primary)
|
||||
: analysisErrorWithCleanupFailure(
|
||||
primary,
|
||||
closed.error,
|
||||
"instrument_with_frida",
|
||||
).bindCleanupDependencies([primary, closed.error]);
|
||||
return err(
|
||||
signal?.aborted
|
||||
? analysisErrorWithCancellation(
|
||||
failure,
|
||||
new AnalysisCancelledError("instrument_with_frida", {
|
||||
cause: signal.reason,
|
||||
}),
|
||||
)
|
||||
: failure,
|
||||
);
|
||||
}
|
||||
|
||||
closeSession(sessionId: string) {
|
||||
return this.#provider.closeSession(sessionId);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user