mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 16:26:36 +00:00
Return a closeable owner before prerequisite work begins. Share creation across independent callers, join the final departure and late handoffs, retain exact failed resource owners, and gate operation dispatch again after acquisition. Keep reuse admission separate from native health. Move shared provider ownership below application workflows and consolidate failure attribution; remove the superseded lazy client. Validation: 34 focused owner, lazy, composite and session controls; check:fast. Compiled baseline/candidate recording-provider workflow reproduces then fences dispatch after close. No native provider claim. Refs #4561, #4667
98 lines
2.9 KiB
TypeScript
98 lines
2.9 KiB
TypeScript
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
|
|
|
/** Link one resource's confirmed release to its originating analysis error. */
|
|
export class AnalysisCleanupLease {
|
|
#released = false;
|
|
#errors: WeakRef<AnalysisError>[] = [];
|
|
readonly #groups = new Set<AnalysisCleanupLeaseGroup>();
|
|
|
|
/** Whether this exact owner has confirmed release. */
|
|
get released(): boolean {
|
|
return this.#released;
|
|
}
|
|
|
|
readonly cleanupResources: readonly string[] | undefined;
|
|
|
|
constructor(
|
|
private readonly cleanup: () => Promise<void>,
|
|
resources?: readonly string[],
|
|
) {
|
|
this.cleanupResources =
|
|
resources === undefined ? undefined : [...resources];
|
|
}
|
|
|
|
async release(): Promise<void> {
|
|
if (this.#released) return;
|
|
await this.cleanup();
|
|
this.#released = true;
|
|
this.#resolveBoundErrors();
|
|
for (const group of this.#groups) group.ownerReleased(this);
|
|
this.#groups.clear();
|
|
}
|
|
|
|
/** Binding can follow a concurrent retry that already confirmed release. */
|
|
bindCleanupError(error: AnalysisError): void {
|
|
if (this.#released) {
|
|
error.resolveCleanup();
|
|
return;
|
|
}
|
|
this.#errors = [
|
|
...this.#errors.filter((reference) => reference.deref() !== undefined),
|
|
new WeakRef(error),
|
|
];
|
|
}
|
|
|
|
/** Bind one aggregate error to every exact lease in a failed retry set. */
|
|
bindCleanupGroup(group: AnalysisCleanupLeaseGroup): void {
|
|
if (this.#released) {
|
|
group.ownerReleased(this);
|
|
return;
|
|
}
|
|
for (const retained of this.#groups)
|
|
if (!retained.hasLiveError) this.#groups.delete(retained);
|
|
this.#groups.add(group);
|
|
}
|
|
|
|
#resolveBoundErrors(): void {
|
|
for (const reference of this.#errors) reference.deref()?.resolveCleanup();
|
|
this.#errors = [];
|
|
}
|
|
}
|
|
|
|
/** Conjunctive cleanup binding for an error covering multiple exact owners. */
|
|
export class AnalysisCleanupLeaseGroup {
|
|
readonly #error: WeakRef<AnalysisError>;
|
|
readonly #pending: Set<AnalysisCleanupLease>;
|
|
|
|
constructor(error: AnalysisError, owners: readonly AnalysisCleanupLease[]) {
|
|
this.#error = new WeakRef(error);
|
|
this.#pending = new Set(owners);
|
|
if (this.#pending.size === 0) return;
|
|
error.bindCleanupResourceOwner(this);
|
|
for (const owner of this.#pending) owner.bindCleanupGroup(this);
|
|
}
|
|
|
|
/** Unlabelled owners keep attribution unknown; never match resources by text. */
|
|
get cleanupResources(): readonly string[] | undefined {
|
|
if (
|
|
[...this.#pending].some((owner) => owner.cleanupResources === undefined)
|
|
)
|
|
return undefined;
|
|
return [
|
|
...new Set(
|
|
[...this.#pending].flatMap((owner) => owner.cleanupResources ?? []),
|
|
),
|
|
];
|
|
}
|
|
|
|
get hasLiveError(): boolean {
|
|
return this.#error.deref() !== undefined;
|
|
}
|
|
|
|
ownerReleased(owner: AnalysisCleanupLease): void {
|
|
if (!this.#pending.delete(owner) || this.#pending.size > 0) return;
|
|
this.#error.deref()?.resolveCleanup();
|
|
this.#pending.clear();
|
|
}
|
|
}
|