fix: make workspace policy waits selection-safe
This commit is contained in:
@@ -9,41 +9,74 @@ export interface WorkspaceDraft {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
let pendingPolicy: { workspaceId: string; promise: Promise<void>; resolve: () => void } | undefined;
|
||||
let failedPolicy: { workspaceId: string; error: Error } | undefined;
|
||||
type PolicySelection = {
|
||||
workspaceId: string;
|
||||
generation: number;
|
||||
state: "pending" | "ready" | "error";
|
||||
error?: Error;
|
||||
settled: Promise<void>;
|
||||
settle: () => void;
|
||||
superseded: Promise<void>;
|
||||
supersede: () => void;
|
||||
};
|
||||
|
||||
/** In-memory handoff between the footer's policy query and all session entry points. */
|
||||
let selection: PolicySelection | undefined;
|
||||
let nextGeneration = 0;
|
||||
|
||||
function pendingSelection(workspaceId: string): PolicySelection {
|
||||
let settle!: () => void;
|
||||
let supersede!: () => void;
|
||||
return {
|
||||
workspaceId,
|
||||
generation: ++nextGeneration,
|
||||
state: "pending",
|
||||
settled: new Promise<void>((resolve) => { settle = resolve; }),
|
||||
settle,
|
||||
superseded: new Promise<void>((resolve) => { supersede = resolve; }),
|
||||
supersede,
|
||||
};
|
||||
}
|
||||
|
||||
/** In-memory coordination between the footer's selected policy and session creation. */
|
||||
export const workspacePolicyGate = {
|
||||
begin(workspaceId: string): void {
|
||||
if (pendingPolicy?.workspaceId === workspaceId) return;
|
||||
if (failedPolicy?.workspaceId === workspaceId) failedPolicy = undefined;
|
||||
let resolve!: () => void;
|
||||
const promise = new Promise<void>((onResolve) => {
|
||||
resolve = onResolve;
|
||||
});
|
||||
pendingPolicy = { workspaceId, promise, resolve };
|
||||
select(workspaceId: string): void {
|
||||
if (selection?.workspaceId === workspaceId) return;
|
||||
selection?.supersede();
|
||||
selection = pendingSelection(workspaceId);
|
||||
},
|
||||
|
||||
clear(): void {
|
||||
selection?.supersede();
|
||||
selection = undefined;
|
||||
},
|
||||
|
||||
resolve(workspaceId: string): void {
|
||||
if (pendingPolicy?.workspaceId === workspaceId) {
|
||||
pendingPolicy.resolve();
|
||||
pendingPolicy = undefined;
|
||||
}
|
||||
if (failedPolicy?.workspaceId === workspaceId) failedPolicy = undefined;
|
||||
if (!selection || selection.workspaceId !== workspaceId || selection.state !== "pending") return;
|
||||
selection.state = "ready";
|
||||
selection.settle();
|
||||
},
|
||||
|
||||
reject(workspaceId: string): void {
|
||||
if (pendingPolicy?.workspaceId !== workspaceId) return;
|
||||
pendingPolicy.resolve();
|
||||
pendingPolicy = undefined;
|
||||
failedPolicy = { workspaceId, error: new Error("Selected workspace policy could not be loaded.") };
|
||||
if (!selection || selection.workspaceId !== workspaceId || selection.state !== "pending") return;
|
||||
selection.state = "error";
|
||||
selection.error = new Error("Selected workspace policy could not be loaded.");
|
||||
selection.settle();
|
||||
},
|
||||
|
||||
waitFor(workspaceId: string | undefined): Promise<void> {
|
||||
const failed = failedPolicy;
|
||||
if (failed && failed.workspaceId === workspaceId) return Promise.reject(failed.error);
|
||||
const pending = pendingPolicy;
|
||||
return pending && pending.workspaceId === workspaceId ? pending.promise : Promise.resolve();
|
||||
async waitForCurrent<T extends { workspaceId?: string }>(read: () => T): Promise<T> {
|
||||
while (true) {
|
||||
const current = read();
|
||||
const pending = selection;
|
||||
if (!current.workspaceId || pending?.workspaceId !== current.workspaceId) return current;
|
||||
const generation = pending.generation;
|
||||
if (pending.state === "ready") return read();
|
||||
if (pending.state === "error") throw pending.error;
|
||||
await Promise.race([pending.settled, pending.superseded]);
|
||||
const completed = selection;
|
||||
if (!completed || completed.generation !== generation || read().workspaceId !== current.workspaceId) continue;
|
||||
if (completed.state === "error") throw completed.error;
|
||||
if (completed.state === "ready") return read();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user