Repository navigation
Replies: 2 comments
|
补一条 Windows 上的实测触发路径,正好落在你第 2 点举的 现场Windows 11,
也就是说:第一次受伤是 EMFILE,第二次及以后看到的是锁超时。报错信息指向的是第二次受伤,排查时容易顺着"锁残留"往下查,而漏掉"为什么会残留"。 概率性复现:观测到 4 次挂 2 次。另外用裸 Node 打开同一路径 3 毫秒成功,可直接证伪"路径/权限有问题"这条线。 本地补丁(三处,跨 0.1.5-rc.1 → 0.1.6-alpha.2 → 0.1.7-rc.2 三个版本、16 天,每次升级重打)// 1. isLockContention:EMFILE 是启动风暴下的暂时性争用,不是永久失败
if (code === "EMFILE") return true;
// 2. 熬过启动风暴
const DEFAULT_LOCK_WAIT_MS = 6e4; // 上游是 2e3
// 3. withFileLock:catch 里打 partial 标志,循环内清掉自己的半成品锁
// (open 成功、write 失败会留下空锁;不清掉,下一次 open(wx) 会死等自己的残留)
} catch (error) {
if (error?.code === "EMFILE") partial = true;
if (!await isLockContention(error, lockPath)) { ... }
}
if (partial) {
await rm(lockPath, { force: true });
partial = false;
}补丁叠在 0.1.6/0.1.7 新增的 EPERM 重试分支之上(保留上游逻辑,不覆盖)。代价是启动变慢:URL 出现在 21-30 秒,正常约 10 秒。 两点补充
|
|
Another data point: still reproducible on Desktop 0.2.0-rc.2 (nightly, Windows 11 26300, Electron 44 / Node 24.18.1). It hit me on two consecutive days. How the empty lock appeared in my case: it's the same EMFILE path @KongFUN2018 described, but the trigger wasn't plugin imports. It was Every later start then failed the same way: In the Desktop app this either leaves the main window hidden (separate report: #9610) or shows "DeepSeek Harness is unavailable" after ~64 s. I had already renamed the same 0-byte lock away the day before, and it came back. I'd add one point to the fixes already proposed in this thread: |
Uh oh!
There was an error while loading. Please reload this page.
现象
一台机器上常驻的
dsh --profile web服务(launchdKeepAlive)从 2026-09-12 起每次启动都失败,被 launchd 反复拉起,两周里重启了 26252 次,日志涨到 58 MB。每次的报错都一样:现场的锁文件是空的(0 字节,没有 PID),创建时间 2026-09-12 22:20:32;
.credentials.yaml本身从 08-31 起没被改过。也就是说,某个进程独占创建了锁、还没写进 PID 就退出了,这把锁此后再没人能拿到,也没人能回收。手动删掉锁文件后恢复。复现(0.1.7-rc.1 实测)
原因(三处叠加,均已在 master
477b4f4上核对)BrowserAuth.create→initializeSecret(packages/client/connection/src/browser-auth.ts)每次都调credentials.modifyRecord(AUTH_RECORD_KEY, …)。记录已存在时回调返回undefined、什么也不写,但credentials-local的modifyRecord无条件先进withFileLock(this.spec.filename, …, { waitMs: DOCUMENT_LOCK_WAIT_MS })(30 秒)。所以一把残留的凭据锁 =connection这个 required 插件激活失败 = 整个 web 宿主起不来,而且每次重启都一样。withFileLock用writeFile(lockPath, `${process.pid}\n`, { flag: 'wx' })创建锁:这是"独占创建 + 写入"两步。进程在两步之间退出,就留下空锁;另外,如果写入本身失败(例如ENOSPC/EIO),isLockContention判为非争用、直接抛出,已经创建的空锁文件也不会被删除。takeOverExitedLock/holderExited只在内容严格匹配^\d+\n$且 PID 探测为ESRCH时接管;注释写明"其他内容是正在写入或被截断,证明不了持有者的任何情况",于是空锁永远被当作"有人持有"来等待,直到超时。也就是说,第 2 点产生的锁,正是第 3 点不会回收的锁;在 master 上,上面的复现依然会失败(按代码推断:holderExited('')返回false,withFileLock等满 30 秒后抛出)。建议的修法(结构性的,而不是在报错处加补丁)
<pid>\n先写进一个唯一的临时文件,再用link(temp, lockPath)原子地发布(EEXIST即争用),最后删除临时文件。这样"残缺的锁"这个状态不再可能出现,master 已有的ESRCH接管就能覆盖所有崩溃情形,写入失败也不会留下空锁。initializeSecret先读已有记录(无锁),只有记录不存在时才走modifyRecord创建(回调里已有对current的复核,天然是 double-check)。稳态启动就不再依赖写锁,一把残留锁最多影响"首次创建密钥"的那一次启动。相关讨论:#7158(超时报错应给出持锁 PID 和处理办法)、#7696(孤儿锁读 PID 回收,master 已实现)、#7634(孤儿 profile 锁导致 settings 迁移静默丢失)。本 issue 的新情况是:空锁(这两处提议和 master 的实现都处理不了)以及凭据锁让 web 宿主整体无法启动。
环境
dsh0.1.2-rc.1 源码(a66e470)以 launchdKeepAlive常驻,DSH_HOME=~/.dsh46a7f68),macOS,Node v22.22.3477b4f4(2026-09-24)All reactions