Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🛠️ 解决的问题
修复 Plugin/VCPTaskAssistant/task-center-data.json 在高频任务调度、多 Agent 并发超时回调或进程异常重启时,偶发性损坏并永久退化为 ~ 119 字节(空壳初始状态),导致用户的定时任务与所有调度执行历史全量丢失的问题。
🔍 根本原因分析
非排他写入与截断竞态(O_TRUNC Write Tearing):
原实现中 saveData() 直接通过裸写 fsPromises.writeFile(DATA_FILE, ...) 进行持久化,且无互斥排队锁。当巡检或多个 Agent 同时超时/完成时,多个写操作几乎在同一微秒触发。当较短的 payload 覆盖较长文件时,操作系统底层页缓存刷新与截断发生竞争,导致合法闭合符号 } ] } 之后残留了前一次写入的重尾残渣(例如在第 144,658 字符位留下 42 字节尾部文本),使得文件语法损坏(Unexpected non-whitespace character after JSON)。
自杀式 Catch 降级与覆盖雪崩:
loadData() 在捕获到 JSON.parse 抛出的语法异常后,将内存对象直接降级置空为 createDefaultData()。在服务冷启动初期,loadData().then(() => rebuildScheduler()) 必定被调用,而 rebuildScheduler 内部又会调用 saveData()。这导致原本只是瞬时语法错误的磁盘文件,在开机数秒内被反手覆写成了 119 字节的空壳,物理抹杀了用户数据。
前端全量覆盖缺乏防护:
updateConfig() 缺乏防空校验。若前端页面在断网或组件挂载初期尚未拿到完整列表便误触发保存,提交的 tasks: [] 会直接清空后端任务库。
🛡️ 核心改动说明
参考了 Plugin/RAGDiaryPlugin/SemanticGroupManager.js 的官方成熟实践并进行了工业级增强:
FIFO Promise 串行写入队列(saveQueue):
在模块内引入单链式 Promise 排队写入队列,所有并发进入的 saveData() 严格先进先出依次处理,彻底消除多 Agent 并发超时的写入竞争。
UUID 临时文件 + OS 级原子重命名(Atomic Rename):
数据写入先落盘至 task-center-data.json..tmp 独立临时文件,写入成功后通过 fs.rename 进行操作系统级原子指针替换。从底层物理消除重尾残渣与半字节截断。
安全熔断与 .bak 镜像自愈闭环:
每次成功落盘前,自动将上一版完好数据轮转备份为 task-center-data.json.bak;
loadData() 遇到主文件损坏时,优先自动尝试从 .bak 镜像自愈恢复,不丢失用户心血;
若主备双损,系统抛出严重错误并激活 saveDisabled = true 熔断锁,坚决拒绝将内存默认空壳写回磁盘,最大程度保护物理现场;
增加恶性覆写拦截:若原磁盘文件 > 10KB 而内存待写入内容 < 1KB 且无任务,触发安全熔断拒绝落盘。
updateConfig 防坍塌守卫:
若当前系统中存在有效任务(tasks.length > 0),而传入的配置试图将其覆写为空数组且未显式指定 forceEmpty: true 确认标记,后端直接拦截并报错,防止前端误操作抹库。
🧪 验证与测试
沙箱极限压力复现:
编写 50 路高频交错并发写入压测脚本(交替用 150KB 与 30KB 负载轰击):旧版代码稳定在第 7769 字符处复现 Unexpected non-whitespace character after JSON;新版排队原子重命名方案 50 路并发 100% 结构完好无残渣。
生产环境全用例闭环测试(verify_production.mjs):
Case 1 (正向用例):真实 169KB 数据冷启动加载,5 个核心任务与 200 条调度历史 100% 无损装载。
Case 2 (并发用例):连续 20 次密集并发写盘,落盘 JSON 语法 100% 合法,无撕裂。
Case 3 (容灾自愈):现场注入 144,658 字符位同款重尾残渣破坏主文件,启动时 0.3 秒无缝从 .bak 镜像自愈拉起。
Case 4 (防坍塌守卫):模拟前端误提交空 tasks: [],守卫成功拦截清空操作。
语法校验:node -c 检查通过,无任何语法或运行期兼容性问题。