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
101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
import type { TransferStatus, TransferTask } from "../../domain/models";
|
|
|
|
export const DEDICATED_RESUME_LARGE_HISTORY_THRESHOLD = 4_096;
|
|
export const DEDICATED_RESUME_CHILD_UPDATE_BATCH_SIZE = 512;
|
|
|
|
export interface DedicatedResumeChildUpdateBatcher {
|
|
push(task: TransferTask): void;
|
|
flush(): void;
|
|
}
|
|
|
|
export interface DedicatedResumeProgressBatcher<T> {
|
|
push(progress: T): void;
|
|
finish(): void;
|
|
}
|
|
|
|
/**
|
|
* A restarted directory can retain tens of thousands of exception rows. The
|
|
* store intentionally performs full history compaction on each upsert, so
|
|
* feeding it one child transition at a time becomes quadratic. Keep only the
|
|
* latest state for each retained child and compact in fixed-size batches.
|
|
*/
|
|
export function createDedicatedResumeChildUpdateBatcher(deps: {
|
|
getTaskCount: () => number;
|
|
hasTask: (taskId: string) => boolean;
|
|
upsertTasks: (tasks: readonly TransferTask[]) => void;
|
|
}): DedicatedResumeChildUpdateBatcher {
|
|
const pending = new Map<string, TransferTask>();
|
|
const flush = () => {
|
|
if (pending.size === 0) return;
|
|
const batch = [...pending.values()];
|
|
pending.clear();
|
|
deps.upsertTasks(batch);
|
|
};
|
|
return {
|
|
push(task) {
|
|
const shouldBatch = !!task.parentTaskId
|
|
&& deps.getTaskCount() >= DEDICATED_RESUME_LARGE_HISTORY_THRESHOLD
|
|
&& deps.hasTask(task.id);
|
|
if (!shouldBatch) {
|
|
deps.upsertTasks([task]);
|
|
return;
|
|
}
|
|
pending.set(task.id, task);
|
|
if (pending.size >= DEDICATED_RESUME_CHILD_UPDATE_BATCH_SIZE) flush();
|
|
},
|
|
flush,
|
|
};
|
|
}
|
|
|
|
/** Only rows still owned by an active resume may accept a deferred rAF sample. */
|
|
export function canApplyDedicatedResumeProgress(status: TransferStatus): boolean {
|
|
return status === "pending" || status === "queued" || status === "transferring";
|
|
}
|
|
|
|
/**
|
|
* Coalesce renderer progress without letting a callback outlive the resume
|
|
* invocation that scheduled it. finish() preserves the newest sample once,
|
|
* cancels the scheduled paint, and permanently rejects late callbacks.
|
|
*/
|
|
export function createDedicatedResumeProgressBatcher<T>(deps: {
|
|
requestFrame: (callback: FrameRequestCallback) => number;
|
|
cancelFrame: (handle: number) => void;
|
|
canApply: () => boolean;
|
|
apply: (progress: T) => void;
|
|
}): DedicatedResumeProgressBatcher<T> {
|
|
let pending: T | undefined;
|
|
let frame: number | null = null;
|
|
let finished = false;
|
|
|
|
const applyPending = () => {
|
|
const progress = pending;
|
|
pending = undefined;
|
|
if (progress !== undefined && deps.canApply()) deps.apply(progress);
|
|
};
|
|
const flushFrame = () => {
|
|
frame = null;
|
|
if (finished) return;
|
|
applyPending();
|
|
};
|
|
|
|
return {
|
|
push(progress) {
|
|
if (finished) return;
|
|
pending = progress;
|
|
if (frame == null) frame = deps.requestFrame(flushFrame);
|
|
},
|
|
finish() {
|
|
if (finished) return;
|
|
finished = true;
|
|
if (frame != null) {
|
|
deps.cancelFrame(frame);
|
|
frame = null;
|
|
}
|
|
// Preserve the final durable checkpoint while the row is still active.
|
|
// The caller can now publish its completed/failed/attention result with
|
|
// no scheduled callback left that could overwrite the terminal state.
|
|
applyPending();
|
|
},
|
|
};
|
|
}
|