Files
BakNRet/docs/adr/0007-run-lock-via-file-handle.md
T
Shuery 3a3a57a6a1 docs: 决策记录 8 条、变更日志、README 的验收与口令两节
0007 与 0008 是本次改造新产生的决策(原计划 6 条)。它们同样满足「后人会问为什么」:把运行锁改成互斥体、或给库里 27 个函数补上 SupportsShouldProcess,都是看起来更规范的错法。

CHANGELOG.md:把 README 里那 33 行「相对旧版修了什么」整节搬过去,并补上本次改造的记录(11 条修复 + 6 条结构与规范,每条修复都写明现象与现状)。README 那一节换成指针。

README 新增两节:「验收与静态分析」(六个层次、Run-RealSmoke 为什么独立存在、以及那 6 条格式规则默认 Disabled 这个容易漏掉的事实);「口令放在哪里」(默认留空不是遗漏,附迁移与验证命令)。

.markdownlint.json 照 PowerShell 主仓库的实践(default true、行长 240),只把 MD024 从关闭改成「仅同级不重复」,因为变更日志需要重复标题。顺带按 .editorconfig 把 .md 的 BOM 去掉。
2026-09-27 10:01:54 +08:00

1.6 KiB
Raw Blame History

运行锁用独占文件句柄,而不是命名互斥体

Backup.ps1 与 Restore.ps1 都会写 manifest.json,也都会在备份目录里用 <归档>.tmp 这个名字生成临时归档。计划任务与手动运行撞在一起时,两边会互相覆盖对方的账本、把彼此的 临时归档当成自己的。计划任务的 -MultipleInstances IgnoreNew 只挡住"计划任务之间", 挡不住手动运行,所以需要一把跨进程的锁。

用独占文件句柄(FileShare.None 打开 Backups\.baknret.lock)而不是命名互斥体:

  • 句柄由内核持有,进程被杀 / 崩溃时自动关闭,锁自动释放 —— 不会留下需要人工清理的陈旧锁;
  • 命名互斥体要跨会话(计划任务在另一个会话里跑,互斥体是会话局部的)就得用 Global\ 前缀, 而那需要额外权限;
  • 文件系统的锁不区分会话与终端,计划任务与手动运行天然互相看见。

拿不到锁就直接失败(退出码 1 + 明确消息),不等待:单个条目压缩可能十几分钟,"等它跑完" 对用户来说和挂住没区别。锁文件里写明持有进程(pid / 起始时间 / 主机 / 用户)——"到底是谁 占着"这个问题不该靠猜。

只读模式不取锁(-DryRun / -WhatIf / -VerifyOnly):它们一个字节都不写。

代价:锁文件是备份目录里的一个额外文件(以 . 开头,归档枚举与孤儿审计都只看 *.7z, 不受影响);Backups/ 被手工删除时锁也随之消失(这没关系,它本来就是运行期的)。