feat: WSL/远程文件树与外部变更监听(第二层,基于 #236)#239
Merged
LarryZhu-dev merged 4 commits intoJun 7, 2026
Merged
Conversation
打开 \\wsl.localhost\ 下的文件时,应用会自动把文件所在目录作为工作区并用 chokidar 递归监听。chokidar 遍历到目录中的 Linux 软链接(如 *.so.0)时会对其 lstat,而 Windows/Node 对经 9P 重定向器暴露的 WSL 软链接做 lstat 会返回 EISDIR (底层是 9P 不支持读取该软链接的 reparse 数据,返回 Incorrect function,被 libuv 映射成了 EISDIR)。该错误没有任何地方处理,冒泡成主进程 uncaughtException,于是弹出 "A JavaScript error occurred in the main process" 并使文件区卡死。 修复采用双保险: - workspacePath.ts:isRemoteWorkspacePath 不再依赖 isWindows 做前置短路。该模块运行在 渲染进程(nodeIntegration:false),那里 process.platform 不可用,isWindows 恒为 false,会让原有的 WSL/远程路径防护永远失效。去掉该短路后,防护真正生效,从源头避免对 WSL 目录自动加载工作区与递归监听。UNC 路径(\\wsl.localhost\、\\wsl$\、\\server\share) 本身是 Windows 专有写法,不会出现在其他平台的合法路径里,无需平台判断即可安全识别。 - ipcBridge.ts:给 file watcher 与 directoryWatcher 各补一个 "error" 监听。chokidar 出错时会 emit "error",没有监听器时会冒泡成主进程 uncaughtException。补上后任何 watcher 错误都只记录、不再使应用崩溃,对所有文件系统(不止 WSL)都更健壮。 注:本次修复保证不再崩溃,但 WSL 文件暂时不会自动加载左侧文件树(防护跳过了)。后续会做 第二层改进,让 WSL 文件也能正常显示文件树。
§0 实测 2026-06-06 (Debian11 / Node v22 / Windows host):
A 完整建树无 EISDIR -- PASS:
- 生产 scanDirectory 在含 symlink/.so链/大目录/深目录 的 WSL 树上建 115 节点,
0 吞错 / 0 EISDIR / 0 uncaught
- 新发现: WSL symlink 从 Windows 侧完全读不透
lstat/readlink=EISDIR, stat(跟随)/readFile=ENOENT (含指向真实存在的目标)
-> symlink 仅能由 readdir d_type 识别, 拿不到 mtime, 无法解引用/打开
B fs.watchFile 在 9P 可靠性 -- PASS (全绿):
追加 / 原子替换(mv,新inode) / 同mtime仅改size(size触发) /
删除(mtime=0) / 删后重建(跟踪存活) / 同秒多写 全部 DETECTED
据 A 缩范围 (spec §4.3/§6.2/§8):
- WSL 分支默认丢弃 symlink (避免死节点/点开 ENOENT), 重指向不可知文档化为局限
- isSymbolicLink() 分支只对 local/SMB 有效
- WSL symlink inert 显示移入 §8 defer
第二层:撤销第一层对 WSL/远程路径的"跳过自动加载工作区",让 WSL 文件也能 显示文件树并在外部变更时提示重载。事件式 fs.watch 在 9P 上启动即 EISDIR, 故 WSL 改走轮询;本地/SMB 保持 chokidar 实时。依据 §0 前置实测(已 PASS)。 新增 src/main/wslWatch.ts(不依赖 electron,回调注入,便于单测): - 路径分流 classifyWatchTarget / normalizeWatchPath(还原 \\?\UNC\ 防误判重开 EISDIR) - scanDirectory(WSL 丢弃读不透的 symlink;本地/SMB 加 isSymbolicLink 分支) - WslDirectoryWatcher:per-dir interval + in-flight guard + epoch 丢弃迟到结果 + 快照(path+mtime+size)diff + 不可达不清树 + 300ms debounce - WslFileWatcher:per-window union 引用计数 + fs.watchFile + mtime=0 删除语义 + size 兜底 + focus checkWindow 兜底 + before-quit dispose 接入 ipcBridge.ts:watchDirectory/file:watch 按 classify 分流;getDirectoryFiles 改用模块 scanDirectory;app browser-window-focus / before-quit / 窗口关闭清理。 renderer:workspacePath.shouldAutoLoadWorkspace 撤销远程跳过;useWorkSpace 远程 空树也置锁(避免 tabs 变化反复重扫慢 9P) + refreshWorkSpace 加 in-flight 互斥。 测试:node:test 22 个(纯函数 + 两个管理器,假依赖) 全过;另用真实 fs.watchFile + snapshotDirectory 在真 \\wsl.localhost\ 集成验证 9/9(目录增删/文件追加替换/focus/ 引用计数)。新增 pnpm test 脚本。 注:GUI 视觉确认(文件树渲染 / 重载弹框)需本机 pnpm run dev 验证。
Contributor
|
@zen010101 感谢你的贡献! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概述
第一层(#236)止血:打开
\\wsl.localhost\下的文件不再因EISDIR崩溃,代价是 WSL 路径被跳过自动加载——没有左侧文件树、也不提示外部变更。第二层(本 PR)恢复功能:让 WSL/远程文件也能 (1) 显示左侧文件树;(2) 外部进程修改文件时重新加载。
为什么 WSL 走轮询
在
\\wsl.localhost\(9P)上实测:fs.watch(chokidar 底层)EISDIR,不可用fs.watchFile(Node 轮询)readdir({withFileTypes})+stat因此按路径分流:仅
\\wsl.localhost\/\\wsl$\走轮询;本地与 SMB 保持 chokidar 实时,不退化。前置实测结论(spec §0,已 PASS)
scanDirectory在含 symlink /.so链 / 大目录 / 深目录的真实 WSL 目录上建树,0 EISDIR / 0 uncaught(readdir用 d_type 分类不lstat;真实 dir/file 的stat正常)。fs.watchFile在 9P 可靠:含"同 mtime 仅改 size"也能检出(比较含 size)、删除回调mtime=0、删后重建跟踪存活。lstat/readlink→EISDIR,stat/readFile→ENOENT(与相对/绝对、指向文件/目录无关)。故 WSL 分支丢弃 symlink(本地/SMB 仍正常显示)。实现要点
src/main/wslWatch.ts(新增,不依赖 electron,回调注入便于单测):classifyWatchTarget+normalizeWatchPath(还原\\?\UNC\前缀,防误判重开 EISDIR);scanDirectory(WSL 丢弃 symlink;本地/SMB 加isSymbolicLink()分支);WslDirectoryWatcher:per-dirsetInterval+ in-flight guard + epoch 丢弃迟到结果 + 快照(path+mtime+size) diff + 不可达不清树 + 300ms 去抖;WslFileWatcher:per-window union 引用计数 +fs.watchFile+mtime=0删除语义 + size 兜底 + 窗口 focus 主动比对兜底 +before-quit清理句柄。src/main/ipcBridge.ts:watchDirectory/file:watch按 classify 分流;getDirectoryFiles改用模块scanDirectory;browser-window-focus/before-quit/ 窗口关闭清理。src/renderer/utils/workspacePath.ts:shouldAutoLoadWorkspace撤销对远程的跳过(§0 已证建树安全)。src/renderer/hooks/useWorkSpace.ts:远程空树也置加载锁(避免 tabs 变化反复整树重扫拖慢 9P);refreshWorkSpace加 in-flight 互斥。测试
node:test22 个(纯函数 + 两个 watcher 管理器,注入假依赖)全过;新增pnpm test脚本。fs.watchFile+snapshotDirectory在真\\wsl.localhost\上验证目录增删 / 文件追加替换 / focus / 引用计数,9/9 通过。已知局限(文档化,不在本层解决)
Z:\等映射盘:靠第一层 error handler 兜底。完整设计与评审记录见
docs/spec-wsl-file-watching-layer2.md。