Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
/**
|
|
* Process-wide pause latches for SFTP transfers.
|
|
*
|
|
* Must outlive any React owner (SFTP panel / vault page). Directory walks and
|
|
* stream workers wait on these latches; the global transfer center sets them
|
|
* on Pause and clears them on Resume whether or not a panel is mounted.
|
|
*/
|
|
|
|
const pausedIds = new Set<string>();
|
|
const barriers = new Map<string, { promise: Promise<void>; resolve: () => void }>();
|
|
// A completed child's history row can disappear while its worker still waits
|
|
// for the folder to resume. Keep the paused tree independent of UI history.
|
|
const pausedChildren = new Map<string, Set<string>>();
|
|
|
|
function ensureBarrier(taskId: string): { promise: Promise<void>; resolve: () => void } {
|
|
let barrier = barriers.get(taskId);
|
|
if (barrier) return barrier;
|
|
let resolve!: () => void;
|
|
const promise = new Promise<void>((r) => {
|
|
resolve = r;
|
|
});
|
|
barrier = { promise, resolve };
|
|
barriers.set(taskId, barrier);
|
|
return barrier;
|
|
}
|
|
|
|
export function isTransferPauseLatched(taskId: string): boolean {
|
|
return pausedIds.has(taskId);
|
|
}
|
|
|
|
export function isTransferOrRootPauseLatched(rootTaskId: string, taskId?: string): boolean {
|
|
return pausedIds.has(rootTaskId) || (!!taskId && pausedIds.has(taskId));
|
|
}
|
|
|
|
/** Latch pause for a task id. Idempotent; creates a waiter barrier. */
|
|
export function latchTransferPause(taskId: string): void {
|
|
pausedIds.add(taskId);
|
|
ensureBarrier(taskId);
|
|
}
|
|
|
|
/** Release pause and wake every waiter. Idempotent. */
|
|
export function releaseTransferPause(taskId: string): void {
|
|
pausedIds.delete(taskId);
|
|
const barrier = barriers.get(taskId);
|
|
if (!barrier) return;
|
|
barriers.delete(taskId);
|
|
barrier.resolve();
|
|
}
|
|
|
|
export function releaseTransferPauseTree(rootTaskId: string, childIds: readonly string[] = []): void {
|
|
const children = new Set([...childIds, ...(pausedChildren.get(rootTaskId) ?? [])]);
|
|
pausedChildren.delete(rootTaskId);
|
|
releaseTransferPause(rootTaskId);
|
|
for (const id of children) releaseTransferPause(id);
|
|
}
|
|
|
|
export function latchTransferPauseTree(rootTaskId: string, childIds: readonly string[] = []): void {
|
|
if (childIds.length > 0) {
|
|
const children = pausedChildren.get(rootTaskId) ?? new Set<string>();
|
|
for (const id of childIds) children.add(id);
|
|
pausedChildren.set(rootTaskId, children);
|
|
}
|
|
latchTransferPause(rootTaskId);
|
|
for (const id of childIds) latchTransferPause(id);
|
|
}
|
|
|
|
export async function waitUntilTransferPauseReleased(taskId: string): Promise<void> {
|
|
while (pausedIds.has(taskId)) {
|
|
const barrier = ensureBarrier(taskId);
|
|
await barrier.promise;
|
|
}
|
|
}
|
|
|
|
export async function waitWhileTransferOrRootPaused(
|
|
rootTaskId: string,
|
|
taskId?: string,
|
|
): Promise<void> {
|
|
while (isTransferOrRootPauseLatched(rootTaskId, taskId)) {
|
|
const latchId = pausedIds.has(rootTaskId) ? rootTaskId : (taskId as string);
|
|
await waitUntilTransferPauseReleased(latchId);
|
|
}
|
|
}
|
|
|
|
/** Test helper — clear all latches between cases. */
|
|
export function resetTransferPauseLatchesForTests(): void {
|
|
const ids = [...pausedIds];
|
|
for (const id of ids) releaseTransferPause(id);
|
|
pausedIds.clear();
|
|
barriers.clear();
|
|
pausedChildren.clear();
|
|
}
|
|
|
|
export function listTransferPauseLatchesForTests(): string[] {
|
|
return [...pausedIds].sort();
|
|
}
|