// fsproxy 执行子进程:从 stdin 读一条 JSON 请求,执行单个文件操作,stdout 逐行 JSON 上报 // {t:"hb",phase,bytes,total}(按 progressIntervalMs 节流,阶段切换与阶段完成立即报)/ {t:"ok",result} / {t:"err",code,message}。 // SIGTERM → 中止当前操作(copy 会清掉自己的 partial)后退出;挂死在系统调用里时由父进程 SIGKILL 回收。 // 只依赖 node 内置模块,`deno run --no-config` 毫秒级启动。编译二进制可在入口按 --role fsworker 调 fsWorkerMain()。 import { toFsError } from "./errors.ts"; import { type FsOpRequest, type FsProgress, runFsOp } from "./fsops.ts"; const enc = new TextEncoder(); function send(obj: unknown): void { try { Deno.stdout.writeSync(enc.encode(`${JSON.stringify(obj)}\n`)); } catch { // 父进程已退出:无处可报 } } /** 阻塞 JS 线程(测试用,模拟卡死的系统调用:信号监听器也不会运行) */ function blockForever(): never { const a = new Int32Array(new SharedArrayBuffer(4)); for (;;) Atomics.wait(a, 0, 0); } export async function fsWorkerMain(progressIntervalMs = Number(Deno.env.get("UMT_FS_PROGRESS_INTERVAL_MS") ?? 1000)) { const req = JSON.parse(await new Response(Deno.stdin.readable).text()) as FsOpRequest; const ac = new AbortController(); Deno.addSignalListener("SIGTERM", () => { if (req._test?.ignoreTerm) return; ac.abort(); // 操作卡在系统调用里时 abort 解不开:给清理一点时间后直接退出 setTimeout(() => Deno.exit(143), 500); }); let lastAt = 0; let lastPhase = ""; const progress = (p: FsProgress) => { const hang = req._test?.hangAfterBytes; const now = Date.now(); // 阶段切换、阶段完成(bytes 到 total)立即报,其余按间隔节流——否则最后一跳可能被节流吞掉 const phaseDone = p.total > 0 && p.bytes >= p.total; if ( p.phase !== lastPhase || phaseDone || now - lastAt >= progressIntervalMs || (hang !== undefined && p.bytes >= hang) ) { lastAt = now; lastPhase = p.phase; send({ t: "hb", ...p }); } if (hang !== undefined && p.bytes >= hang) blockForever(); }; if (req._test?.hangAfterBytes === 0) { send({ t: "hb", phase: "start", bytes: 0, total: 0 }); blockForever(); } try { const result = await runFsOp(req, { signal: ac.signal, progress }); send({ t: "ok", result }); } catch (e) { const fe = toFsError(e); send({ t: "err", code: ac.signal.aborted ? "ABORTED" : fe.code, message: fe.message, ...(fe.detail.path ? { path: fe.detail.path } : {}), ...(fe.detail.syscall ? { syscall: fe.detail.syscall } : {}), }); } Deno.exit(0); } if (import.meta.main) await fsWorkerMain();