Files
NetMesh/application/state/sftp/transferPauseLatch.ts
zhaolei 3c72efcb7f
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
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

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();
}