Repository navigation
1.4.3
dsh-version-update 1.4.3
本版修掉五处会静默出错的问题:树修复会误删或覆盖正在运行的安装、更新锁在极端交错下会让两个宿主同时安装、被超时抛弃的安装会去装旧版本并把新版本记成成功,以及审计记录把一次纯清理写成「快照恢复」。
修复
- 退休目录的年龄改从「它所在的目录」读取。 npm 的 retire 是一次改名(arborist
lib/retire-path.js),而改名只更新父目录的 mtime、被改名目录自身的 mtime 原样不动。于是一秒前刚被退休的目录仍报告它上次被写入的时间——对几天前安装的包就是几天前——10 分钟新鲜度闸门把它读成「陈旧」,引导修复会删掉仍在运行的 reify 的退休副本,或在 npm 还在写的时候把快照恢复上去。现在取两者中更年轻的一个,只会更保守。实测:一次改名后子目录仍是 30.0 天,父目录变成 0.0 秒。测试改用真实的renameSync(不再用utimesSync给子目录造年龄),这正是该 bug 能存活的原因:夹具伪造了闸门本该推断出的年龄。 - 延后清扫的定时器不再拖住退出中的宿主。 它现在与其他所有定时器一样
unref()。上一条修复让常见情形(刚刚退休的目录)把等待表达为整个闸门,于是用户退出后进程还会存活最多十分钟。这是测试挂了两分钟才暴露出来的。 - 所有会写安装树的路径现在都拿机器级锁。 原先只有失败后修复拿锁;引导修复和延后清扫只看
updater.busy(),而那只覆盖本进程——于是另一个宿主正在安装时(桌面端在更新、用户又在终端起了一个dsh web),它的树可能被就地重建。年龄闸门不能替代锁:它约束的是删除,约束不了恢复分支。发现树被占用时是延后而非丢弃,且延后有界。 - 被硬超时抛弃的那一轮安装,不会再把自己的陈旧目标装上去。 所有守卫都用共享的
task.state判断「我这轮还在跑吗」,而settle()与start()都会整体替换task对象——于是在快照还在拷贝时被抛弃的流水线,从那个绑定里读到的是下一轮的running,为自己的陈旧版本 spawn npm,然后把新一轮判成成功。面板与history.json双双记下一次从未发生的安装,而用户真正点的版本一次都没被 spawn。现在每轮持有单调身份,settle()对已不再存活的那一轮完全惰性。 - 更新锁的发布是原子的,抢占带身份校验。 记录原先在
open(path,'wx')创建路径之后才写入,于是锁路径有一个系统调用时长的空文件窗口;空锁与损坏的锁不可区分,而每个读者都把损坏当陈旧抢走——两个同时启动的宿主可能都以为自己拿到了锁。现在记录先写进私有临时文件、再link进锁路径。抢占也是同样的两步结构:判定陈旧之后裸rmSync可能删掉期间替换上来的那条记录(也就是新持有者的锁),两个宿主随即安装进同一棵全局树。现在先rename成私有墓碑、核对实际拿到的是不是被判陈旧的那条,不是就放回去。 - 审计记录不再把一次纯清理叫作「快照恢复」。
restored原先对每一次修复都置位,于是一次只删残留目录的修复在面板上读作「快照恢复」——一条从未发生过的版本迁移。现在事件种类(repair)与它做了什么(restored、removed)分开,这也让一次只报错的修复不会再渲染成普通的失败安装。
测试
262 用例通过,tsc --checkJs 干净。五处修复都用「把修复回退掉、断言必须失败」实测过。已发布 tarball 上的 10 条行为断言全部通过。
完整对比:v1.4.2...v1.4.3