Repository navigation
Replies: 1 comment
|
这条我端到端复现了,你说的量级是对的 —— 一行 34 个字符的输入可以跑 69.8 秒。但有两处要收紧,其中一处会直接影响别人按你的报告复现。 1. 根因在上游,但放大它的是 DSH 的引擎选择出问题的规则在 DSH 一个字都没改(
⇒ 走的是 V8 的 irregexp,会回溯;而 2. ⭐ 触发条件比你写的更严:必须缩进,且位于
|
| 形态 | 规则被调用时 at |
耗时 |
|---|---|---|
{[]*…x(行首) |
≥ 15 | ≈ 0 ms |
\t{[]*…x(缩进一行) |
0 | 1084–1089 ms |
⇒ 可靠的最小复现是 一行位于 struct { 体内、有缩进、且行内没有 } 的 {[]*…x:
type S struct {
{[]*[]*[]*[]*[]*[]*[]*[]*x
}这条建议直接补进报告,否则维护者按你原文的最小字符串跑不出来,会以为报告不成立。
3. 量级:本机实测(用 highlight.ts 的原始选项)
createJavaScriptRegexEngine({ forgiving: true, regexConstructor: p => defaultJavaScriptRegexConstructor(p, { lazyCompileLength: Infinity }) }) + tokenizeMaxLineLength: 1000,一次 codeToTokens:
[]* 次数 n |
行长 | wall |
|---|---|---|
| 6 | 22 | 76 ms |
| 7 | 25 | 137 ms |
| 8 | 28 | 1094 ms |
| 9 | 31 | 8731 ms |
| 10 | 34 | 69820 ms |
每 +1 次 []* 约 ×8 —— 与你 "n=8 ≈ 0.8s、n=10 ≈ 50s" 的倍率与量级都对得上。
trace 里的变换特征也吻合:@shikijs/engine-javascript@4.4.3 的 defaultJavaScriptRegexConstructor 实际调 toRegExp(pattern, { global: true, hasIndices: true, lazyCompileLength: 3e3, rules: { asciiWordBoundaries: true, … } }) —— asciiWordBoundaries: true 正好解释你看到的 \bconst\b/\bchan\b 保留 \b,而 \s→\p{space}、\w→[\p{L}\p{M}\p{N}\p{Pc}]。
4. 现有的三层护栏全都挡不住(这是最该写进报告的一条)
TOKENIZE_MAX_LINE_LENGTH = 1000(highlight.ts:286)—— 你的行只有 26–34 个 UTF-16 单元,根本不进降级分支- shiki 默认
tokenizeTimeLimit = 500(@shikijs/primitive@4.4.3 dist/index.mjs:620)—— 我实测把tokenizeTimeLimit设成 500 / 50 / 1 ms,同一输入 wall = 8669 / 8651 / 8654 ms,完全不受影响。原因在@shikijs/vscode-textmate@10.0.2 dist/index.js:1812-1821:while (!STOP) { if (timeLimit !== 0) { … if (elapsedTime > timeLimit) return … } scanNext() }—— 预算只在两次scanNext()之间检查,单次exec不可抢占 forgiving: true(highlight.ts:272)只吞"编译失败",不治运行时回溯
⇒ 所以这不是"护栏参数没调好",是护栏在结构上无法覆盖这种单次超长执行。
5. 一个你可能没意识到的边界:没有配置项能关掉高亮
ui-primitives 是 cordis-free 的纯组件包(package.json 无 dsh 字段、无 plugin config),CodeBlockProps 也没有 highlight 开关。仅有的三种隐式降级:语言 hint 不在 LANG_ALIASES(highlight.ts:196-255)、grammar 尚未 lazy load 完、单行 ≥ 1000 单元。所以"给用户一个开关"这件事今天没有落点,需要新增面。
顺带把"滚动卡死"的机制说准:useViewportHighlighting.ts:7,49,71 表明每个代码块的激活是 IntersectionObserver 首次进入视口时一次性(激活后 unobserve,组件生命周期内不再取消)——所以滚动只是把那块内容第一次带进视口,代价集中在这一次(外加流式期间 highlight.ts:544 每帧重算尾部行)。
6. 修复通道:仓库已经有现成的 patch 机制
你已经验证了上游有界化方向,我建议优先推上游;DSH 侧最符合本仓惯例的做法是 pnpm.patchedDependencies——这个机制和目录都已存在:
pnpm-workspace.yaml:108-115(已有 7 条 patch,含exceljs@4.4.0等第三方包)patches/目录
⇒ 可以加一条 patches/@shikijs__langs@4.4.3.patch 修掉 dist/go.mjs 里那条规则,比在 highlight.ts 里做形态预检更干净。次选才是对 fence 文本做廉价预检后走 plain 兜底。
7. 用户侧立即可用的规避
把该代码块的 fence 语言标签改掉(```go → ``` 或别的语言):grammarForHint 直接返回 undefined,走 plain 文本,根本不会加载 Go 语法。
8. 版本
你写 0.1.5-rc.2、并称 0.2.0-rc.2 仍复现 —— 最新线仍未修:origin/master = d743267388(= dsh-v0.2.1-alpha.2)的 ui-primitives/package.json:42,44 是 shiki ^4.4.3 / @shikijs/langs ^4.4.3,而 4.4.3 的 Go 规则文本与你引的逐字相同。我单独取 origin/master lock 里解析出的 4.4.3 核对(与本机 node_modules 里的 4.3.1 对比过:两份 go.mjs 都是 51828 字节,规则文本一致),所以这条不是版本差异造成的。
Uh oh!
There was an error while loading. Please reload this page.
Summary
在 dsh web 中打开含较多 Go 代码块的历史会话,滚动时会触发 Web 前端内置 Shiki 高亮所使用的 Go TextMate 语法里的一条灾难性回溯(catastrophic backtracking)正则,主线程被长时间占满,页面卡死并反复弹出 “Page Unresponsive(页面无响应)”。后端与插件均正常,是纯前端渲染问题。
Reproduction
启动 dsh web(
@deepseek-ai/dsh@0.1.5-rc.2),打开任意包含 Go 代码块(```go``)的历史会话;或新建会话发送一个 Go 代码块。让代码块中出现一行形如
{之后跟 ≥ 10 次连续[]*、且该行内没有}的文本。最小字符串:{[][]*[][]*[][]*[][]*[][]*[][]*[][]*[][]*[][]*[][]*x实测量级(Node 直接执行翻译后的正则,指数增长):
{+[]*×8 +x→ ≈ 0.8s{+[]*×10 +x→ ≈ 50s在该会话页面滚动(可视区代码块被反复挂载/高亮),页面随即卡死。
Current behavior
主线程 CPU 被一条正则占满。Chrome Performance trace 采样统计:单个
RegExp占全部采样的 27.3%,加上其执行入口findNextMatchSync的 3.3%,合计约 非空闲 CPU 的 75%((45944+5500)/68659采样)。trace 中该正则的 source 为(const 规则):
以及 struct 字段规则对应的:
对应 Go 语法源(
dist/assets/langs/go-*.js)中的规则(多条共享同一chan类型片段):repository/struct_variables_types_fields/patterns/3/matchrepository/const_assignment/patterns/0/match(var_assignment等同源)受影响文件(均为官方前端产物):
@deepseek-ai/dsh-web-frontend/dist/assets/langs/go-C27-OAKa.js.../dist/assets/vendor-CCJJTK99.js(Shiki + 正则执行).../dist/assets/index-BKQ_L1z6.js(codeToTokens高亮加载)该 Go 语法在最新的
@deepseek-ai/dsh-web-frontend@0.2.0-rc.2中与0.1.5-rc.2的go-*.jsmd5 完全一致(4b2f87cf1d348561a636dd937140140a),即最新版本仍存在。Expected behavior
struct_variables_types_fields/patterns/3的外层(...)+、以及共享的chan片段(...)+{0,1}收敛为(...){0,1}),消除((X?)Y?)+这类结构;findNextMatchSync仅 1.5%),可作为修复方向的参考。Environment
@deepseek-ai/dsh@0.1.5-rc.2(并确认0.2.0-rc.2仍复现)@deepseek-ai/dsh-web-frontend@0.1.5-rc.2(Shiki 代码高亮)Trace-20261009T195249.json.gz,如需可提供)All reactions