Skip to content

fix(speedtest): 覆盖感知并发编排,杜绝多入口子集测速静默漏测/错测#277

Merged
Sway-Chan merged 4 commits into
dododook:mainfrom
Sway-Chan:fix/speedtest-concurrency-coverage-coalesce
Jul 9, 2026
Merged

fix(speedtest): 覆盖感知并发编排,杜绝多入口子集测速静默漏测/错测#277
Sway-Chan merged 4 commits into
dododook:mainfrom
Sway-Chan:fix/speedtest-concurrency-coverage-coalesce

Conversation

@Sway-Chan

Copy link
Copy Markdown
Collaborator

根因

SpeedTestService.currentTest 单飞按「是否存在」无条件复用,不看在飞测速是否覆盖本次请求的 serverIds。注释假设「双入口都全量、同一 set」——被 per-group 本组测速(子集)/单节点⚡/子集先-全量后 打破。

穷举(入口 × 分组):

场景 旧行为
全部测速 多入口并发(首页/托盘/页级) coalesce 同一次
同分组本组测速重复点 按钮 disabled 挡
全部 → 本组(子集) 本组 ⊆ 全部,coalesce
本组/单节点 → 全部 全部撞子集 currentTest → 只测子集、其余静默漏测
不同分组连续点(A 跑时点 B) B 撞 A → B 拿 A 的节点结果、B 自己从未测

修复(覆盖感知 coalesce + 串行链)

currentTest 旁记 currentTestIds(在飞节点集)。新请求 S:

  • S ⊆ 在飞集 → 复用同一次(零重跑、避免双临时 sing-box 端口冲突)
  • 否则 → 串行链在在飞测速之后、用自身 set 完整测到(不同分组/子集先-全量后 各自被测、永不并发双 sing-box、无漏测/错测)

不采「打断旧测速」:无 AbortController,中途 teardown 临时 sing-box + 释放探针端口本身有 race,比串行更糟。

测试

新增 speed-test-coalesce.test.ts(桩 doTestAllServers):覆盖复用 / 未覆盖串行(B 用自身 set 拿自身结果)/ 子集先-全量后(全量测全集)/ 归零 4 例。gate tsc + eslint + jest 2895 绿。真机待验:快速切 tab + 测速中点他组/全部。

Sway-Chan added 4 commits July 9, 2026 09:24
根因:SpeedTestService.currentTest 单飞按「是否存在」无条件复用,不看在飞测速是否覆盖
本次请求的 serverIds。注释假设「双入口都全量、同一 set」——被 per-group 本组测速(子集)/
单节点⚡/子集先-全量后 打破:
- 不同分组连续点(A 跑时切 tab 点 B):B 撞 A 的 currentTest → B 拿 A 的节点结果、B 自己节点从未测
- 本组/单节点 先 → 全部 后:全部撞子集 currentTest → 只测子集、其余节点静默漏测,UI 却报「完成」

修复:currentTest 旁记 currentTestIds(在飞节点集)。新请求 set S:
- S ⊆ 在飞集 → 复用同一次(零重跑、避免双临时 sing-box 端口冲突)——全部+本组子集/同 set/多入口全量
- 否则 → 串行链在在飞测速之后跑、用自身 set 完整测到——不同分组/子集先-全量后 各自被测、永不并发双 sing-box

不采「打断旧测速」:无 AbortController,中途 teardown 临时 sing-box + 释放探针端口本身有 race,比串行更糟。

新增 speed-test-coalesce.test.ts(桩 doTestAllServers):覆盖复用/未覆盖串行/子集先全量后/归零 4 例。
gate tsc + eslint + jest 2895 绿。
问题:多入口/快速连点不同分组时,每次 handleSpeedTest 各自弹独立 loading→success toast,
堆叠出 N 个「开始测速」+ N 个「测速完成」。

修复(不走禁用全部按钮的捷径、保留排队多组能力):新增 speed-test-toast 聚合协调器——
引用计数 + 固定 toast id,首个开始弹 loading、仍有在跑显剩余组数、全部结束才弹一次
success/error;跨 useSpeedTest 实例共享(模块级单例)。

- use-speed-test handleSpeedTest 改用 beginAggSpeedTest/endAggSpeedTest
- 新增 i18n servers.speedTestingCount「测速中 · {{count}} 组」(5 locale,parity 1475)
- speed-test-toast.test(mock sonner):单组/多组并发/失败聚合/批次隔离/end 多于 begin 5 例

配合 dododook#277 后端串行链:后端保正确性(不漏测)、前端聚合保观感(不堆叠)。gate tsc+eslint+jest 2900 绿。
问题(用户反馈):快速连点不同分组时,排队组的按钮显当前在跑那组的数字(串台);
「共 N 组」在「本组A + 全部」场景语义不对等。

根因:进度是单条全局 IPC 流(无 test 标签),被多个 per-hook speedProgress 消费,后端串行
→ 所有按钮镜像当前在跑那次的数字。非 A/B 片面,任意 2+ 并发 handleSpeedTest 皆串。

修复:
- 进度收敛到聚合 toast 唯一权威:coordinator 订 per-node result 事件,以「已测唯一节点 /
  全部请求节点并集」显进度。并集去重(本组A+全部 → 并集=全部;coalesce B⊆A → 并集=A;
  节点二测 serverId 去重)天然处理所有入口组合,无「N 组」歧义。
- 按钮去掉 tested/total 数字 → 仅「测速中」二态(串台源即那串数字);三处消费点(本组/全部/首页)统一。
- 单节点⚡并入聚合(连点不堆终端 toast),行内 spinner 仍由 testingServerIds 驱动。
- i18n speedTestingCount(组)→ speedTestingNodes「测速中 · {{tested}}/{{total}} 节点」(5 locale,parity)。
- coordinator 用相对路径 import api(jest 无 @/ mapper,别名致单测不可 mock)。

speed-test-toast.test 重写(mock result 订阅):单次/并集去重/本组+全部/失败聚合/批次隔离 5 例。gate 2900 绿。
- .nd-flag 从右上(top:-12px)移右下(bottom:-12px):避让首行右侧 SpeedBadge 延迟徽标
  (原右上重叠遮挡测速结果,用户反馈)。国旗按名称映射(getCountryCode 正则表,自建/订阅/组网通用)。
- getCountryCode 台湾/台北/新北/tw/taiwan → 返 'tw'(显 🇹🇼)而非 'cn'(用户指定)。
@Sway-Chan Sway-Chan merged commit 7c00922 into dododook:main Jul 9, 2026
2 checks passed
@Sway-Chan Sway-Chan deleted the fix/speedtest-concurrency-coverage-coalesce branch July 9, 2026 02:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant