十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

197、【Agent】【OpenCode】TuiThreadCommand handler:stop 的幂等清理

197、【Agent】【OpenCode】TuiThreadCommand handler:stop 的幂等清理 【声明】本博客所有内容均为个人业余时间创作所述技术案例均来自公开开源项目如GithubApache基金会不涉及任何企业机密或未公开技术如有侵权请联系删除标题197、【Agent】【OpenCode】TuiThreadCommand handlerstop 的幂等清理背景上篇 blog【Agent】【OpenCode】类型谓词filter 的静默搭档拆了thread.ts:131-135给 Worker 造 env 的一行多语法Object.entries把process.env摊平成元组数组.filter筛掉值为undefined的条目其中(entry): entry is [string, string]是类型谓词——运行时行为不变只负责让 filter 之后的类型从[string, string | undefined][]收窄成[string, string][]最后Object.fromEntries拼回干净对象交给 Worker。196 拆的是启动链路上一处语法本篇继续沿thread.ts往下把 192 篇里一笔带过的幂等 stop专门展开stop 到底在清理什么、为什么只执行一次、什么时候被触发OpenCode192 篇讲从参数到 Worker 就绪时第五关出现过这样一句话“stop用stopped标志保证只执行一次”。当时一笔带过本篇把这套清理逻辑掰开一个只在finally里调用一次的函数为什么还要费劲加幂等标志// thread.ts:155-168letstoppedfalse// ① 一次性闭包标志conststopasync(){if(stopped)return// ② 已执行过 → 短路stoppedtrue// ③ 首次进入即置位process.off(uncaughtException,error)// ④ 撤掉前文挂的 3 个监听process.off(unhandledRejection,error)process.off(SIGUSR2,reload)awaitwithTimeout(client.call(shutdown,undefined),5000)// ⑤ 优雅关闭5s 超时.catch((error){Log.Default.warn(worker shutdown failed,{error:errorinstanceofError?error.message:String(error),})})worker.terminate()// ⑥ 兜底强杀}幂等是啥调用 N 次 调用 1 次幂等idempotent指重复调用的效果与单次调用完全相同。对 stop 来说调用次数行为第 1 次撤监听 → 发 shutdown → terminate完整清理第 2 次stopped已置 true → 直接 return什么都不做第 3、4、N 次同上全部空操作这样即便 stop 被触发多次不会重复发 shutdown RPC、不会重复terminate()、不会重复撤监听——避免对 Worker 做二次清理引发竞态或异常。为什么只执行一次stopped 标志三步走关键就在闭包变量stoppedthread.ts:155逻辑是经典的一次性守卫第一次调用stoppedfalse → if (stopped) 不成立 → stopped true ← 置位 → 执行完整清理流程 之后的任何调用stoppedtrue → if (stopped) 成立 → return直接结束stopped只存在于 handler 的闭包作用域里stop 每次调用读到的是同一个变量所以第一个赢其余短路。⚠️stop 什么时候触发finally 里的最后一棒直接触发点只有一处thread.ts:217-219try{awaittui({...})// 主循环TUI 退出才 resolve}finally{awaitstop()// ← 无论正常退出还是抛异常finally 必执行}tui()返回的是一个 Promiseapp.tsx:117只有 TUI 整个生命周期结束才 resolve。所以finally 保证只要 tui() 返回正常或异常stop 就一定跑。那TUI 退出又是怎么传导到 stop 的完整链路如下用户在 TUI 里触发退出退出键 / 任务完成 / 出错 → exit.tsx 的 exit()重置窗口标题、destroy 渲染器、FlushInputBuffer、写错误/提示文本 → await input.onExit?.() (exit.tsx) → app.tsx 的 onExitunguard?.() resolve() (app.tsx:127-130) → tui() 的 Promise resolve → thread.ts 的 await tui() 返回 → finally { await stop() } → 撤监听 → shutdown RPC5s 超时→ terminate → handler 外层 finallyunguard?.()幂等→ process.exit(0)一句话stop 只在 TUI 生命周期结束那一刻被 finally 触发一次从用户按下退出到 worker 被回收中间隔了 exit() → onExit → resolve → await 返回 → finally 五个环节。为什么幂等是防御性设计有人会问stop 当前不就一个调用点thread.ts:218吗stopped标志是不是多余不是。这是防御性设计三个理由防未来扩入口一旦以后增加信号处理、错误恢复等清理路径也调 stop幂等保证无论几个入口先后触发完整清理只跑一遍防 async 穿插JS 虽单线程但 stop 是asyncawait client.call(shutdown)挂起期间可能插入其它逻辑再触发 stop标志能挡住第一次清理进行中又被调一次与 unguard 同源印证Win32 守卫的unhookwin32.ts:114-126用同样的done标志保证unguard幂等——它被 app.tsx:128 和 thread.ts:221两处调用全靠标志防重放。维度stopunguardunhook标志stoppeddone调用点thread.ts:218finallyapp.tsx:128 thread.ts:221幂等效果只清理一次 worker只还原一次控制台设计目的防重复 shutdown/terminate防重复 SetConsoleMode 还原一句话记忆stop 用stopped标志实现调用 N 次 1 次首次撤监听、发 shutdown5s 超时、terminate 强杀只在tui()的 finally 里被触发一次幂等是为了防御未来多入口 async 穿插——与unguard的done标志是同一套设计思想。OK本篇先到这里如有疑问欢迎评论区留言讨论祝各位功力大涨技术更上一层楼更多内容见下篇 blog【Agent】【OpenCode】TuiThreadCommand handlertransport 双形态的触发与误区
返回列表