mirror of
https://github.com/morluto/rea.git
synced 2026-11-13 18:22:42 +00:00
fix(session): retain unknown cleanup across mixed failures
This commit is contained in:
1 parent
25a08ed639
commit
eb24da4481
4 files changed
+98
-11
No files matched your search
@@ -1132,6 +1132,34 @@ it("joins cancelled readiness finalization and shutdown before admitting another
|
||||
}
|
||||
});
|
||||
|
||||
it.each(["alpha", "beta"])(
|
||||
"retains unknown owned-resource cleanup when %s has no completion signal",
|
||||
async (unknownOwner) => {
|
||||
const unknown = new ProviderAdapterError(unknownOwner, "close_binary", {
|
||||
userMessage: "cleanup completion unknown",
|
||||
});
|
||||
const tracked = new ProviderCleanupError("rea", ["owned-reader"], {});
|
||||
let released = false;
|
||||
const registry = new AnalysisProviderRegistry(
|
||||
["alpha", "beta"].map((id) => ({
|
||||
...candidate(id).provider,
|
||||
closeOwnedResources: async () => {
|
||||
if (!released) return err(id === unknownOwner ? unknown : tracked);
|
||||
if (id !== unknownOwner) tracked.resolveCleanup();
|
||||
return ok(null);
|
||||
},
|
||||
})),
|
||||
);
|
||||
const closed = await registry.closeOwnedResources();
|
||||
if (closed.ok) throw new Error("Expected incomplete registry close");
|
||||
expect(closed.error.cleanupIncomplete).toBe(true);
|
||||
released = true;
|
||||
expect(await registry.closeOwnedResources()).toEqual(ok(null));
|
||||
expect(tracked.cleanupIncomplete).toBe(false);
|
||||
expect(closed.error.cleanupIncomplete).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it("joins every candidate and retains physical failure with exact mixed cleanup owners", async () => {
|
||||
const alpha = candidate("alpha").provider;
|
||||
const beta = candidate("beta").provider;
|
||||
|
||||
@@ -252,16 +252,15 @@ export class AnalysisProviderRegistry {
|
||||
const distinct = [...new Set(failures)];
|
||||
if (distinct.length === 0) return ok(null);
|
||||
if (distinct.length === 1) return err(distinct[0]!);
|
||||
return err(
|
||||
new ProviderCleanupError(
|
||||
"rea",
|
||||
[...new Set(distinct.flatMap((failure) => failure.cleanupResources))],
|
||||
{ reason: distinct.map((failure) => failure.message).join("; ") },
|
||||
{ cause: new AggregateError(distinct) },
|
||||
).bindCleanupDependencies(
|
||||
distinct.filter((failure) => failure.cleanupIncomplete),
|
||||
),
|
||||
const failure = new ProviderCleanupError(
|
||||
"rea",
|
||||
[...new Set(distinct.flatMap((failure) => failure.cleanupResources))],
|
||||
{ reason: distinct.map((failure) => failure.message).join("; ") },
|
||||
{ cause: new AggregateError(distinct) },
|
||||
);
|
||||
if (distinct.every((failure) => failure.cleanupIncomplete))
|
||||
failure.bindCleanupDependencies(distinct);
|
||||
return err(failure);
|
||||
}
|
||||
|
||||
/** List the unique deep-operation declarations without probing candidates. */
|
||||
|
||||
@@ -913,7 +913,8 @@ export class BinarySession
|
||||
next,
|
||||
"close_binary",
|
||||
);
|
||||
resourceFailure.bindCleanupDependencies([previous, next]);
|
||||
if (previous.cleanupIncomplete && next.cleanupIncomplete)
|
||||
resourceFailure.bindCleanupDependencies([previous, next]);
|
||||
}
|
||||
if (!closed.ok && resourceFailure !== undefined) {
|
||||
const combined = analysisErrorWithCleanupFailure(
|
||||
@@ -921,7 +922,8 @@ export class BinarySession
|
||||
resourceFailure,
|
||||
"close_binary",
|
||||
);
|
||||
combined.bindCleanupDependencies([closed.error, resourceFailure]);
|
||||
if (closed.error.cleanupIncomplete && resourceFailure.cleanupIncomplete)
|
||||
combined.bindCleanupDependencies([closed.error, resourceFailure]);
|
||||
return err(combined);
|
||||
}
|
||||
if (!closed.ok) return closed;
|
||||
|
||||
@@ -982,6 +982,64 @@ it.each([0, 1])(
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["binary", "client"],
|
||||
["binary", "files"],
|
||||
["bundle", "client"],
|
||||
["bundle", "files"],
|
||||
] as const)(
|
||||
"keeps unknown %s close completion when the %s owner has no cleanup signal",
|
||||
async (targetKind, unknownOwner) => {
|
||||
const tracked = new ProviderCleanupError(identity.id, ["owned-reader"], {});
|
||||
const unknown = new ProviderAdapterError(identity.id, "close_binary", {
|
||||
userMessage: "cleanup completion unknown",
|
||||
});
|
||||
let released = false;
|
||||
let failCleanup = false;
|
||||
const closeOwner = async (owner: "client" | "files") => {
|
||||
if (!failCleanup) return ok(null);
|
||||
if (!released) return err(owner === unknownOwner ? unknown : tracked);
|
||||
if (owner !== unknownOwner) tracked.resolveCleanup();
|
||||
return ok(null);
|
||||
};
|
||||
const client: AnalysisClient = {
|
||||
execute: async () => ok(createAnalysisExecution(null, binaryIdentity)),
|
||||
close: () => closeOwner("client"),
|
||||
};
|
||||
// Exercise the public session close composition with independently owned
|
||||
// file cleanup; the file owner's failure shape is the regression boundary.
|
||||
class SessionWithFileCleanup extends BinarySession {
|
||||
override closeFileResources() {
|
||||
return closeOwner("files");
|
||||
}
|
||||
}
|
||||
const session = new SessionWithFileCleanup(
|
||||
createTestProviderRouter(() => client),
|
||||
new InvestigationRecords(),
|
||||
{ createClient: () => ({ client, capabilities: [] }) },
|
||||
);
|
||||
if (targetKind === "binary") {
|
||||
expect(await session.open(process.execPath)).toMatchObject({ ok: true });
|
||||
} else {
|
||||
const rootPath = await mkdtemp(join(tmpdir(), "rea-resource-close-"));
|
||||
temporaryDirectories.push(rootPath);
|
||||
await writeFile(join(rootPath, "Info.plist"), "<plist><dict/></plist>");
|
||||
expect(await session.openBundleResources(rootPath)).toMatchObject({
|
||||
ok: true,
|
||||
});
|
||||
}
|
||||
failCleanup = true;
|
||||
const closed = await session.close();
|
||||
if (closed.ok) throw new Error("Expected incomplete close");
|
||||
expect(closed.error.cleanupIncomplete).toBe(true);
|
||||
released = true;
|
||||
expect(await session.close()).toMatchObject({ ok: true });
|
||||
expect(tracked.cleanupIncomplete).toBe(false);
|
||||
expect(closed.error.cleanupIncomplete).toBe(true);
|
||||
expect(await session.status()).toMatchObject({ cleanup_pending: false });
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["health", "cancellation"] as const)(
|
||||
"updates early resource-open %s cleanup after its candidate retries",
|
||||
async (failureStage) => {
|
||||
|
||||
Reference in new issue
Block a user