mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
* refactor(errors): give caller aborts a typed reason at the adapters Squashes 908813551f, 87858de4dc (fix(mcp): preserve cancellation reasons in handlers) and fa151dc3f6 (test(server): race handler-entry gates against the triggering tool call) onto main without #10566's pre-squash commits. * refactor(errors): give owner-initiated aborts a typed reason outside the browser adapters (#10692) * refactor(errors): give owner-initiated aborts a typed reason outside the browser adapters * refactor(verify): keep the abort-reason scan receiver-independent Playwright's route.abort takes a network error code; the one call site now names its default code instead of the guard exempting a variable name. * refactor(browser): give owner-initiated CDP aborts typed reasons (#10706) * refactor(browser): give owner-initiated CDP aborts typed reasons * fix(browser): give the page-side request fetch page-native abort reasons The request callback runs in the page through page.evaluate and cannot reach REA's modules. Define its deadline and cleanup reasons as DOMExceptions inside the callback, and make the test fake evaluate the callback from its source so module imports are unavailable, as in a page. * test(process): expect the reader close's typed owner reason Closing the Darwin run-token reader aborts its compilation with OwnerCancellation.ownerClosed since the owner-abort change in this PR; the boundary test still expected the default DOMException. * fix(server): report HTTP session replacement and shutdown to pending tools as an owner close
83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
import { OwnerCancellation } from "../domain/analysisErrorCore.js";
|
|
import { AbortedWait, waitForAbortable } from "./AbortablePromise.js";
|
|
|
|
/** The final departing caller retains the producer's complete cleanup result. */
|
|
export type SharedStartupWait<Value> =
|
|
| { readonly status: "completed"; readonly value: Value }
|
|
| { readonly status: "cancelled"; readonly reason: unknown }
|
|
| { readonly status: "stopped"; readonly value: Value };
|
|
|
|
type StartupSettlement<Value> =
|
|
| { readonly status: "pending" }
|
|
| { readonly status: "completed"; readonly value: Value }
|
|
| { readonly status: "rejected" };
|
|
|
|
/** Own one startup operation while its callers wait independently. */
|
|
export class SharedStartupAttempt<Value> {
|
|
readonly #controller = new AbortController();
|
|
readonly promise: Promise<Value>;
|
|
readonly #signal: AbortSignal;
|
|
#waiters = 0;
|
|
#settlement: StartupSettlement<Value> = { status: "pending" };
|
|
|
|
constructor(
|
|
start: (signal: AbortSignal) => Promise<Value>,
|
|
/** An owning operation's cancellation is independent of individual waiters. */
|
|
ownerSignal?: AbortSignal,
|
|
) {
|
|
this.#signal =
|
|
ownerSignal === undefined
|
|
? this.#controller.signal
|
|
: AbortSignal.any([ownerSignal, this.#controller.signal]);
|
|
this.promise = start(this.#signal);
|
|
void this.promise.then(
|
|
(value) => {
|
|
this.#settlement = { status: "completed", value };
|
|
},
|
|
() => {
|
|
this.#settlement = { status: "rejected" };
|
|
},
|
|
);
|
|
}
|
|
|
|
/** Signal consumed by the startup producer, never by an individual waiter. */
|
|
get signal(): AbortSignal {
|
|
return this.#signal;
|
|
}
|
|
|
|
/** Completion includes the producer's final cleanup, not just its abort. */
|
|
get settled(): boolean {
|
|
return this.#settlement.status !== "pending";
|
|
}
|
|
|
|
/** Parsed completion facts are owned here, rather than copied into adapters. */
|
|
get settlement(): StartupSettlement<Value> {
|
|
return this.#settlement;
|
|
}
|
|
|
|
/**
|
|
* Stop startup when its owning provider closes, regardless of active waits.
|
|
* The reason is distinct from a caller's cancelling its wait.
|
|
*/
|
|
close(): void {
|
|
if (!this.settled)
|
|
this.#controller.abort(OwnerCancellation.startupOwnerClosed);
|
|
}
|
|
|
|
/** A peer's departure leaves the producer running; final departure joins cleanup. */
|
|
async wait(signal?: AbortSignal): Promise<SharedStartupWait<Value>> {
|
|
this.#waiters += 1;
|
|
try {
|
|
const waited = await waitForAbortable(this.promise, signal);
|
|
if (!(waited instanceof AbortedWait))
|
|
return { status: "completed", value: waited };
|
|
if (this.#waiters > 1)
|
|
return { status: "cancelled", reason: waited.carried };
|
|
if (!this.settled) this.#controller.abort(waited.carried);
|
|
return { status: "stopped", value: await this.promise };
|
|
} finally {
|
|
this.#waiters -= 1;
|
|
}
|
|
}
|
|
}
|