【BUG反馈】8,325个临时目录差点把我的C盘干爆炸了(附修法,官方看一眼啊) #5332
Replies: 2 comments 1 reply
|
English translation for non-Chinese readers: Title: 8,325 temp directories nearly blew up my C: drive %TEMP% filled up my C: driveEvery dsh process creates its own little directory under the OS temp dir ( What's going on
let defaultSpillDir: string | undefined
function privateSpillDir(): string {
defaultSpillDir ??= mkdtempSync(join(tmpdir(), 'dsh-subprocess-'))
return defaultSpillDir
}The dir is created lazily per process; the spill files inside do get removed, but no code anywhere is responsible for removing the directory itself — a repo-wide search finds no cleanup of Left unfixed, this bug will keep biting more usersAs long as dsh doesn't ship a way to clean up after itself, Windows users whose disk fills up will naturally reach for Storage Sense or a third-party temp cleaner — which then deletes the spill dir of a running dsh, and dsh crashes with ENOENT. That's exactly the #2252 / #1961 / #3190 / #5250 / #758 family of threads. Two symptoms, one root cause: the temp dirs have no lifecycle management. Fix the accumulation and you also remove most of the reason people run cleaners against %TEMP% in the first place. Suggested fix
As a bonus, give that parent an "ensure it exists, recreate if missing" helper and route |
|
Verified your source analysis against alpha.2 ( Confirmed — the real gap is
let defaultSpillDir: string | undefined
function privateSpillDir(): string {
defaultSpillDir ??= mkdtempSync(join(tmpdir(), 'dsh-subprocess-'))
return defaultSpillDir
}Created lazily per process; the spill files inside get unlinked, but nothing ever removes the directory itself — no Precision 1 —
Precision 2 — Matches your read: the private temp directory is removed at provider dispose ( Your "two symptoms, one root cause" framing is right — and the crash side still needs its own guard This is the same family as #3190 / #3203 / #5175, which I've analyzed before. On your three proposals:
The recreate-on-miss at the |
Uh oh!
There was an error while loading. Please reload this page.
%TEMP%把C盘干爆
每个 dsh 进程启动后都会在系统临时目录里建一个自己的小目录(
dsh-subprocess-*等),但从来不删。Windows 的 %TEMP% 系统也从来不会自动清。于是它只增不减,我这边高强度用下来就把C盘撑炸了:8,325 个目录,20+GB,微信弹磁盘警告,系统差点起不来。清理干净之后,只是正常用了几个小时,又攒出 20 个。问题情况
packages/subprocess/subprocess-local/src/spawn.ts—privateSpillDir():这个目录是每进程懒创建的,里面的 spill 文件用完会删,但目录本身没有任何代码负责删。全仓搜不到对
defaultSpillDir的清理。同族还有spill-local的dsh-spill-*和sandbox-local的dsh-*(后者正常 dispose 会清,但崩溃/强杀路径会留下,里面还可能有内容——20GB 的大头应该来自这类非正常退出留下的带内容目录)。其实同类问题codex也有,就算定时清也还是没法根治。奇怪的是居然没看到其他同类案例报错,难道其他人用dsh都没有遇见类似的情况吗?我记得codex之前经常干爆用户的C盘,导致不得不搬到别的盘去,还得定时清。这个BUG不处理还是会坑更多用户
因为 dsh 如果自己不加一个打扫自己的功能,Windows 用户磁盘告急后自然会跑系统「存储感知」或第三方清理工具——然后清理工具删掉正在运行的 dsh 的 spill 目录,dsh 直接 ENOENT 崩溃。这正是 #2252、#1961、#3190、#5250、#758 那一批帖子。两件事是同一个根因:临时目录没有生命周期管理。修堆积,顺便就把那批崩溃的诱因削掉一大半。
建议官方修的思路
进程正常退出时删掉自己的 spill 目录。落点——
LocalSubprocessRuntime构造函数里已经注册了 teardown effect 和process.on('exit')监听(subprocess-local/src/index.ts),专门负责 host 退出时终止子进程;在disposeManagedProcesses()之后和 exit 监听里各加一句rmSync(spillDir, { recursive: true, force: true })就行(exit 回调只能同步,rmSync 正好合规)。更干净的做法是把 spill 目录的所有权从模块级变量挪进 Runtime 实例,生命周期跟 ctx 走。spill-local的defaultRoot同理,挂它自己的 service dispose。host 启动时清扫残留目录。关键是怎么判断「目录的主人死没死」——建议用锁文件而不是 PID:每个进程建目录后立刻在里面放一个
owner.lock并保持句柄打开(Windows 独占 share mode,POSIX flock);清扫者对每个候选目录尝试独占打开这个锁,打得开=主人已死可删,打不开=活进程跳过。PID 文件不可靠(PID 会被系统回收重用),锁不会误判。没有锁文件的目录(老版本留的)用 mtime 年龄阈值兜底(比如超过 24 小时)。清扫本身 best-effort + 限流(单次最多扫 N 个),删不掉的(EBUSY/EPERM)留给下次。所有每进程目录统一放进
join(tmpdir(), 'dsh')这个固定父目录。清扫范围从「全 %TEMP% 按前缀猜」变成「只扫自家院子」,永远碰不到别人的文件;用户也一眼能看懂这堆是谁的,想手动删只用删一个目录。顺带,给这个父目录做一个「不存在就重建」的 ensure 函数,
OutputCollector写 spill 文件遇到 ENOENT 时走同一个函数重建重试——正好把 #2252 那批「外部清理删目录→崩溃」的帖子一起修了。All reactions