mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
refactor(errors): require a reason for every analysis cancellation (#10566)
* refactor(errors): require a reason for every analysis cancellation Add cancelledBy, the one constructor for AnalysisCancelledError. Its reason argument is required: an aborted signal, an earlier cancellation, a reason carried from an inner failure, or an OwnerCancellation when REA closed the work's owner itself. Closed adb, apktool, firmware, sqlite, gdb, web-bundle analyzer and binary-session record owners now report OwnerCancellation.ownerClosed instead of an absent reason. Firmware's predecessor wait returns its cancellation as a Result, removing an unreachable reason-less fallback. An architecture guard rejects new direct constructions, with a counted per-file baseline that can only shrink. * refactor(errors): share one aborted-signal predicate with cancelledBy * refactor(process): return a departing caller's reason from a shared startup wait * refactor(providers): build decompiler-provider cancellations with a required reason (#10587) Migrate every AnalysisCancelledError construction under src/ghidra, src/ida, src/cutter, src/jeb and src/kuna to cancelledBy and lower the guard baseline from 671 to 572. Closed Kuna target commands report OwnerCancellation.ownerClosed instead of an absent reason, and Kuna and JEB close abort their lifetimes with that typed reason. * fix(errors): read the reason of any signal-shaped cancellation source * test(ci): cover the counted-baseline comparison behind the architecture guard
This commit is contained in:
1 parent
2f4c588584
commit
85da0bd4ee
51 files changed
+900
-488
No files matched your search
@@ -310,7 +310,11 @@ export class AnalysisCapabilityUnavailableError extends AnalysisError {
|
||||
}
|
||||
}
|
||||
|
||||
/** Caller cancellation won before provider-neutral work completed. */
|
||||
/**
|
||||
* Cancellation, by the caller or by REA closing the work's owner, won before
|
||||
* provider-neutral work completed. Build it with `cancelledBy`, which requires
|
||||
* the reason; `cause` holds that reason.
|
||||
*/
|
||||
export class AnalysisCancelledError extends AnalysisError {
|
||||
readonly _tag = "AnalysisCancelledError";
|
||||
override readonly userMessage: string | undefined;
|
||||
@@ -326,6 +330,85 @@ export class AnalysisCancelledError extends AnalysisError {
|
||||
}
|
||||
}
|
||||
|
||||
/** Why REA ended work on its own authority when no caller abort exists. */
|
||||
export type OwnerCancellationKind =
|
||||
/** The provider, session, or service owning the work was closed or disposed. */
|
||||
"owner-closed";
|
||||
|
||||
/**
|
||||
* A cancellation REA initiated itself, such as a provider closing. It stands
|
||||
* in for a caller abort reason, so it is never mistaken for one.
|
||||
*/
|
||||
export class OwnerCancellation extends Error {
|
||||
override readonly name = "OwnerCancellation";
|
||||
private constructor(readonly kind: OwnerCancellationKind) {
|
||||
super("REA closed the owner of this operation before it completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* One shared reason per kind, so repeated owner cancellations compare equal
|
||||
* the way repeated observations of one caller abort do.
|
||||
*/
|
||||
static readonly ownerClosed = new OwnerCancellation("owner-closed");
|
||||
}
|
||||
|
||||
/** A caller abort reason recovered from an earlier observation of that abort. */
|
||||
export interface CarriedCancellationReason {
|
||||
readonly carried: unknown;
|
||||
}
|
||||
|
||||
/** Mark a reason already observed from the caller's abort, such as an inner failure's recorded reason. */
|
||||
export const carriedReason = (reason: unknown): CarriedCancellationReason => ({
|
||||
carried: reason,
|
||||
});
|
||||
|
||||
/**
|
||||
* Where a cancellation's reason comes from:
|
||||
* - an aborted signal, whose `reason` is the caller's (or parent operation's) reason;
|
||||
* - an earlier `AnalysisCancelledError`, whose reason is carried unchanged;
|
||||
* - a reason carried from another earlier observation;
|
||||
* - an owner cancellation that REA initiated itself.
|
||||
*/
|
||||
export type CancellationReason =
|
||||
| AbortSignal
|
||||
| AnalysisCancelledError
|
||||
| CarriedCancellationReason
|
||||
| OwnerCancellation;
|
||||
|
||||
/** A signal observed aborted, so its `reason` is the cancellation's reason. */
|
||||
export type AbortedSignal = AbortSignal & { readonly aborted: true };
|
||||
|
||||
/** Whether an optional signal has aborted; narrows it to pass to `cancelledBy`. */
|
||||
export const isAborted = (
|
||||
signal: AbortSignal | undefined,
|
||||
): signal is AbortedSignal => signal?.aborted === true;
|
||||
|
||||
/** Options for a cancellation; its cause is always the required reason. */
|
||||
export type CancelledOptions = Omit<AnalysisErrorOptions, "cause"> & {
|
||||
readonly cause?: never;
|
||||
readonly userMessage?: string;
|
||||
};
|
||||
|
||||
// Signals are matched structurally last: callers and tests may pass an
|
||||
// AbortSignal implementation from another realm or a signal-shaped fake.
|
||||
const cancellationCause = (reason: CancellationReason): unknown => {
|
||||
if (reason instanceof AnalysisCancelledError) return reason.cause;
|
||||
if (reason instanceof OwnerCancellation) return reason;
|
||||
if ("carried" in reason) return reason.carried;
|
||||
return reason.reason;
|
||||
};
|
||||
|
||||
/** The one constructor for a cancellation: its reason is required. */
|
||||
export const cancelledBy = (
|
||||
operation: string,
|
||||
reason: CancellationReason,
|
||||
options?: CancelledOptions,
|
||||
): AnalysisCancelledError =>
|
||||
new AnalysisCancelledError(operation, {
|
||||
...options,
|
||||
cause: cancellationCause(reason),
|
||||
});
|
||||
|
||||
/** Provider-neutral operation exceeded its declared execution deadline. */
|
||||
export class AnalysisTimeoutError extends AnalysisError {
|
||||
readonly _tag = "AnalysisTimeoutError";
|
||||
|
||||
Reference in new issue
Block a user