Conversation
…eady registered`
`sync` is subscribed to BOTH the descriptor registry and the sidebar store, and
`SidebarStore.notify()` runs its listeners INLINE — so every preference write,
session switch and state mutation calls `sync` synchronously, several times per
frame. The host registry takes an id inside the registration's effect (its
`ids.add(id)`) while its duplicate guard is a synchronous `ids.has(id)`, so a
`register` call that throws AFTER the host has taken the id leaves the caller
with no handle: `live.set(...)` never runs.
The next `sync` then calls `register` for an id the host still holds and the
guard throws `sidebarRight: tab type id "dsh-better-sidebar:files" is already
registered`, surfacing on a real 0.21.1 + DSH 0.1.7-rc.2 profile as:
[dsh-better-sidebar] native register files error: sidebarRight: tab type
id "dsh-better-sidebar:files" is already registered
Because `reportFailure` (the omdsh-dev#700 line of work) catches it, the symptom is not
a crash — it is a missing takeover plus permanent log noise, and the `files`
explorer silently stays unregistered for that sync.
Two changes:
- `registerFilesKind` and `registerDescriptor` treat an `is already registered`
rejection as "the host already owns this row": the type is reused and the
body/title slots still register, instead of aborting the whole descriptor.
Matching on the message is the only handle available — the host exports no
error class and the id/kind guards are separate `throw new Error` sites.
- A new regression test drives `sync` a second time through a store
notification after a half-failed registration, and asserts no `files`
failure is reported. It fails on the unpatched tree with
`expected [ 'register files' ] to deeply equal []`.
Verified: `pnpm typecheck`, `pnpm exec eslint` on both files, and
`tests/native-surface.spec.ts` (21 tests) all clean. The 4 pre-existing
failures in `plugin-meta` / `smoke` reproduce on an unmodified main checkout.
|
补充一下大肥鱼发现的相同问题的补充: 跨平台独立复现确认在另一套环境上复现了同一个报错,与你的 Windows / node 26 不同平台:
触发条件补充(与"一帧内多次 sync"不完全相同的一条路径):报告者是插件环境变动时必现——即往 profile 里安装/卸载任意插件之后。插件集变更会触发客户端插件整体重挂载,此时新一次激活的 补充证据:残留 id 不是消费方漏包
|
关闭:根因已由 #777 以「释放」而非「吸收」的方式修复感谢这个 PR——它把「半失败注册(宿主已取走 id,调用方拿不到句柄)」这一步说清楚了,而且本 PR 的注释块正是我们最终确认的机制。差别只在怎么收尾:
我们选后者的理由(也是 zangxx66 在评论里点出的风险):吸收之后,当旧持有者随后释放该 id 时不会有人补注册, 另外,触发链比本 PR 描述的多一环,真机日志( 后续:#785 把守护断言改写到注册表事件日志上、补了「部分回滚」覆盖与 |
问题 / Problem
真机(DSH 0.1.7-rc.2 + 本插件 0.21.1)上反复出现:
reportFailure(#700 那条线)把崩溃降级成了日志,所以症状不是白屏,而是该次sync()里files接管静默缺位,外加持续的日志噪音。根因
sync同时订阅了描述符注册表和 sidebar store,而SidebarStore.notify()是内联跑 listener 的 —— 每一次偏好写入、会话切换、状态变更都会同步调用sync,一帧内多次。宿主注册表的时序是错开的:
于是一次
register调用如果在宿主已经取走 id 之后抛出,调用方就拿不到任何句柄(live.set(...)从未执行)。下一次sync再对同一个仍被占用的 id 调register,守卫就抛already registered。改动 / Change
registerFilesKind与registerDescriptor把is already registered视为「宿主已持有该行」:复用该类型,同时继续注册 body/title 槽位,而不是中断整个描述符。用消息匹配是唯一可行的判据 —— 宿主没有导出错误类,id 与 kind 两个守卫是两个独立的
throw new Error。sync,断言不再上报files失败。验证 / Verification
pnpm typecheckpnpm exec eslint(两个改动文件)tests/native-surface.spec.tstests/native-surface + service + builtins回归测试的有效性(这是关键):在未打补丁的
main上,该测试失败并精确复现了线上报错:既有失败(与本 PR 无关):
plugin-meta1 例、smoke3 例(log 分页、revert、cherry-pick)在未改动的 main 上以相同方式失败,已在纯净 checkout 上复现核对;fs-operations的并发用例为负载抖动。环境:Windows / node 26。
备注 / Notes
sync()的 reject 可见,本 PR 处理的是重复注册本身——即 §Problem 里那条链的起点。ids集合:ISidebarRight没有暴露它,且 AGENTS.md §1 禁止依赖非公开面,故采用消息匹配 + 复用降级。