Replies: 3 comments
|
master 错误 32(ERROR_SHARING_VIOLATION)正是 Defender 实时扫描/索引器瞬时持有句柄的典型形态,「同一文件立即重试通常成功」完全符合这个特征——瞬时锁,等几十毫秒就释放。 建议只对可重试错误做有界退避,其余照旧上抛: // win32.ts replaceFileWin32 内
const SHARING = new Set([32 /*ERROR_SHARING_VIOLATION*/, 33 /*ERROR_LOCK_VIOLATION*/])
for (let attempt = 0; ; attempt++) {
if (api.replaceFileW(...) !== 0) {
const lastError = api.getLastError()
if (SHARING.has(lastError) && attempt < 3) {
await setTimeout(50 * (attempt + 1)) // 50/100/150ms
continue
}
throw win32Error('ReplaceFileW', lastError, replaced)
}
break
}不建议换成 |
|
经常遇到这个问题 |
|
我按 #425 的复现范围做了一个可审阅的 fork reference implementation(尚未声称已被上游采用):
实现边界:
验证:Win32 binding 7/7 且 限制也明确记录了:本地没有伪造“真实 Defender 竞态已复现”;真实触发条件仍需要 Windows 原生运行结果和报告者确认。 感谢 @Acidmoon 的完整报告、@FirmaSpring 的边界分析,以及 @liuyib 的独立复现反馈。 |
Uh oh!
There was an error while loading. Please reload this page.
描述
在 Windows 上使用 edit 工具(或
writeText)覆盖一个已存在的文件时,经常直接失败,报错形如:环境
0.1.0-rc.6(npm 全局安装)根因分析
1. 错误串来源与含义
dsh-fs-local的win32Error()(src/fsio.ts,安装包中为lib/index.js34–51 行)只把 Win32 2/3 映射为ENOENT、5 映射为EACCES,其余所有错误码一律映射为EIO。因此消息中的 "EIO" 只是兜底标签,真实错误码是Win32 32 = ERROR_SHARING_VIOLATION("文件正被另一个进程使用,且未授予 FILE_SHARE_DELETE")。2. 调用链
edit/write 覆盖已有文件时,
writeFileAtomic(lib/index.js461 行起)执行:.文件名.<pid>.<uuid>.tmpdir/staging 目录;open("wx", 0o600)写入临时文件 →fsync→close(483–497 行);ReplaceFileW(目标, 临时文件, NULL)(83–86 行,经 koffi 直调 kernel32)——为保留目标文件的 DACL(见设计笔记2026-07-19-windows-atomic-write-dacl-preservation);rename(504–509 行),其余错误(包括 32)直接抛出,无任何重试。3. 为什么失败
ReplaceFileW要求目标文件与替换文件在调用瞬间不被任何进程以不带FILE_SHARE_DELETE的方式打开。Node/libuv 打开文件时带FILE_SHARE_READ|WRITE|DELETE,DSH 自身句柄不会造成此问题。最可疑的占用者是 Windows Defender 实时防护对刚创建的 staging 临时文件的扫描句柄——DSH 在 close 后毫秒级调用ReplaceFileW,扫描尚未结束 → ERROR_SHARING_VIOLATION。这解释了"频率高、非 100%、重试即过"的特征。复现与实测
瞬时锁,无法稳定复现。实测记录(Windows 11 25H2 / build 26200.8875):
toNamespacedPath+ staging 子目录 +wx/fsync/close)连续压测 20 次MoveFileExW(MOVEFILE_REPLACE_EXISTING)(即fs.rename)对照组失败概率与 Defender 扫描时长相关(签名刚更新、目录冷启动时窗口更宽)。
最小压测脚本(与 DSH 相同调用形态):
预期行为
瞬时文件锁(杀软扫描、索引器等)不应导致编辑失败:
MoveFileExW(MOVEFILE_REPLACE_EXISTING)(即fs.rename)——临时文件在写入前已拷贝目标 DACL(lib/index.js485 行),rename 不会丢失 ACL,仅失去ReplaceFileW的元数据保留语义,作为兜底可接受。建议修复
ReplaceFileW失败(至少ERROR_SHARING_VIOLATION=32)增加短退避重试(如 5 次 × 20–50ms)后再报错;EACCES/EBUSY,避免误导性的EIO(2/3→ENOENT、5→EACCES 之外不应全部兜底为 EIO);fs.rename(temp, target)作为兜底(本机实测MoveFileExW始终可用);dsh-fs-localREADME 的 Known Limitations 中补充 Windows 瞬时共享冲突的说明。同类问题的既有修法(供参考)
用户侧缓解(非本 issue 修复范围)
All reactions