Files
makelore/electron/opencode/runtime-config-readiness.ts

108 lines
3.7 KiB
TypeScript

export interface RuntimeConfigGenerationManager {
getRuntimeGeneration?: () => number;
getRuntimeGenerationProvenance?: () => 'unknown' | 'starting' | 'fresh' | 'attached';
}
interface RuntimeConfigPendingLatch {
runtimeGeneration: number;
clearOnFreshGeneration: boolean;
}
const pendingLatches = new WeakMap<object, RuntimeConfigPendingLatch>();
const coordinatorTails = new WeakMap<object, Promise<void>>();
export interface RuntimeConfigCoordinatorLease {
isActive: () => boolean;
isRefreshPending: () => boolean;
markRefreshPending: () => number;
retainRefreshPending: () => number;
}
function pendingForCurrentGeneration(manager: RuntimeConfigGenerationManager): boolean {
const latch = pendingLatches.get(manager as object);
if (!latch) return false;
const runtimeGeneration = manager.getRuntimeGeneration?.() ?? 0;
const provenance = manager.getRuntimeGenerationProvenance?.() ?? 'unknown';
if (
latch.clearOnFreshGeneration
&& runtimeGeneration !== latch.runtimeGeneration
&& provenance === 'fresh'
) {
pendingLatches.delete(manager as object);
return false;
}
return true;
}
export async function withRuntimeConfigCoordinator<T>(
manager: RuntimeConfigGenerationManager,
operation: (lease: RuntimeConfigCoordinatorLease) => Promise<T>,
): Promise<T> {
const key = manager as object;
const previous = coordinatorTails.get(key) ?? Promise.resolve();
let release!: () => void;
const current = new Promise<void>((resolve) => {
release = resolve;
});
const queued = previous.then(() => current);
coordinatorTails.set(key, queued);
await previous;
let active = true;
try {
return await operation({
isActive: () => active,
isRefreshPending: () => active ? pendingForCurrentGeneration(manager) : true,
markRefreshPending: () => {
const runtimeGeneration = manager.getRuntimeGeneration?.() ?? 0;
if (active) pendingLatches.set(key, { runtimeGeneration, clearOnFreshGeneration: true });
return runtimeGeneration;
},
retainRefreshPending: () => {
const runtimeGeneration = manager.getRuntimeGeneration?.() ?? 0;
if (active) pendingLatches.set(key, { runtimeGeneration, clearOnFreshGeneration: false });
return runtimeGeneration;
},
});
} finally {
active = false;
release();
if (coordinatorTails.get(key) === queued) coordinatorTails.delete(key);
}
}
export async function markRuntimeConfigRefreshPending(
manager: RuntimeConfigGenerationManager,
): Promise<number> {
return await withRuntimeConfigCoordinator(manager, async (lease) => lease.markRefreshPending());
}
export async function isRuntimeConfigRefreshPending(
manager: RuntimeConfigGenerationManager,
): Promise<boolean> {
return await withRuntimeConfigCoordinator(manager, async (lease) => lease.isRefreshPending());
}
export async function withRuntimeAcceptanceTimeout<T>(
operation: (signal: AbortSignal) => Promise<T>,
timeoutMs = 10_000,
): Promise<T> {
const controller = new AbortController();
let timeout!: ReturnType<typeof setTimeout>;
let timeoutFallback: ReturnType<typeof setTimeout> | undefined;
const timeoutError = new Error('OpenCode runtime acceptance timed out');
const timedOut = new Promise<never>((_resolve, reject) => {
timeout = setTimeout(() => {
controller.abort(timeoutError);
timeoutFallback = setTimeout(() => reject(timeoutError), 0);
}, timeoutMs);
});
try {
controller.signal.throwIfAborted();
const accepted = operation(controller.signal);
return await Promise.race([accepted, timedOut]);
} finally {
clearTimeout(timeout);
if (timeoutFallback) clearTimeout(timeoutFallback);
}
}