背景
在团队共用测试机或 CI 节点上,tap package install 并发触发很常见。最容易踩的坑不是“下载失败”,而是两个进程同时改安装目录:一个在写文件,另一个在删旧版本,最后留下半更新状态。OpenTAP 在 Package 模块里专门做了一层跨进程互斥,目标很明确:宁可等待,也不要把安装目录写坏。
框架分析
这条链路由三层组成。第一层是 LockingPackageAction,在执行包操作前统一进入 Target/.lock 互斥区;第二层是 FileLock.Create(...),按操作系统选择实现(Windows 用命名 Mutex,Linux 用 flock,macOS 用锁文件轮询);第三层是仓库下载的细粒度锁,FilePackageRepository.FileCopy() 对单个目标包文件再加一次 destination.lock,避免同名文件并发覆盖。
这种“目录级 + 文件级”组合很实用:安装动作串行化保证一致性,单文件写入再加保护,避免中途取消时把半包暴露给后续流程。
实现过程
可复现的代码定位命令如下:
1 | cd /home/ops/clawd/repos/opentap |
从源码看,LockingPackageAction.Execute() 先尝试 WaitOne(0) 快速抢锁;失败后进入最多 2 分钟等待,并同时监听取消令牌。也就是说它不是“死等”,而是带超时和可中断语义。拿到锁后才执行 LockedExecute(...),把真正的安装/卸载逻辑包进临界区。
注意事项
--Unlocked只适合你非常确定不会并发修改同一目录的场景;在共享机器上默认不要开。- macOS 实现注释里明确写了“非线程安全”,同进程多线程混用时要避免复用同一个锁实例。
- 文件复制采用临时文件
.part-<guid>再move,这减少了“读到半文件”的概率,但外部脚本仍应以命令退出码为准,不要只靠文件是否存在判断成功。
小结
OpenTAP 的包管理并发控制不是一个大锁拍脑袋解决,而是把“安装目录一致性”和“单文件写入完整性”分层处理:前者由 LockingPackageAction 兜底,后者由 FilePackageRepository 补强。对产线环境来说,这种设计的价值在于失败可恢复、等待可预期、并发下行为稳定。
关键源码路径:
Package/PackageActions/LockingPackageAction.csPackage/FileLocks.csPackage/Repositories/FilePackageRepository.cs