Skip to content

perf(pack): accelerate large-graph HMR updates#3241

Open
fireairforce wants to merge 11 commits into
nextfrom
zoomdong/fix-hmr-client-scoping
Open

perf(pack): accelerate large-graph HMR updates#3241
fireairforce wants to merge 11 commits into
nextfrom
zoomdong/fix-hmr-client-scoping

Conversation

@fireairforce

@fireairforce fireairforce commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

  • share one Turbopack HMR iterator per resource and deliver partial updates only to WebSocket clients subscribed to that resource
  • harden unsubscribe, reconnect, iterator completion/error, payload cloning, and per-client issue isolation
  • enable per-dynamic-group HMR chunk lists for Utoopack development so the entry subscription no longer contains every lazy chunk
  • add opt-in controls for background persistent-cache snapshots and memory eviction while preserving existing project defaults
  • report timing only after an HMR payload is submitted to a client

Root causes

Ant Design exposes a large eager documentation graph. Its entry HMR chunk list contained 4,628 chunks, and each same-resource subscription previously created another iterator whose updates were broadcast to every socket. A development cache snapshot could also serialize about 138k tasks while a small Markdown update was waiting.

The new dynamic-list mode keeps the entry list at two directly generated chunks and lets loaded dynamic groups register their own lists. Client delivery is now one shared source fanned out only to actual subscribers. dumi can disable background snapshots and eviction only for its scoped demo-HMR mode; all existing Utoopack defaults remain unchanged.

Related changes

Validation

  • @utoo/pack: 73/73 tests passed
  • @utoo/pack-shared: 6/6 tests passed
  • CJS/ESM build, both package typechecks, Biome, schema parsing, and diff checks passed
  • cargo check -p pack-core passed, including turbopack-browser
  • targeted dynamic_chunk_lists Rust snapshot and runtime assertion passed
  • Ant Design production site build passed with 2,659 assets
  • real browser checks covered normal demos, independent demo pages, iframe/off-route subscriptions, source edits, import add/remove, and metadata deletion without full reload or deleted-module errors
  • measured Markdown updates: about 225ms for a one-page first edit with background snapshots disabled; warm multi-page updates about 0.1 to 0.7s

Compatibility

Global BUILDING, BUILT, and SYNC broadcasts remain unchanged. Background persistence and full memory eviction retain their previous defaults unless a consumer explicitly opts out.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors Hot Module Replacement (HMR) client state management by extracting core operations into a new helper module, hmrClientState.ts, and introduces comprehensive unit tests in hmr.test.ts to verify client delivery and subscription lifecycles. Feedback on the changes suggests avoiding direct parameter mutation of the payload object in enqueueTurbopackUpdateForClient to prevent unexpected side effects, and catching potential promise rejections when calling subscription.return?.() to avoid unhandled promise rejections.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/pack/src/core/hmrClientState.ts Outdated
Comment thread packages/pack/src/core/hmrClientState.ts Outdated
@fireairforce
fireairforce marked this pull request as ready for review July 20, 2026 06:09
@fireairforce fireairforce changed the title fix(pack): scope HMR updates to subscribed clients perf(pack): accelerate large-graph HMR updates Jul 20, 2026
@fireairforce

Copy link
Copy Markdown
Member Author

本轮 HMR 安全加固已推送:按 WebSocket/client 精确分发更新,构建期 batch 合并;Node/WASM 增加 expectedVersion 与 validation token 握手;WebSocket 自动重连并恢复订阅;WASM 订阅支持 generation/unsubscribe/RootTask dispose;dev/watch/hot=false 保持旧构建语义。新增 8 个 Chromium HMR 竞态回归并接入 pack CI。验证:Pack 76/76、pack-shared 6/6、HMR E2E repeat 3 为 24/24、动态列表快照 2/2、WASM 完整构建、@utoo/web tsc、相关 cargo check/clippy 均通过。

@fireairforce

Copy link
Copy Markdown
Member Author

已同步最新 next(包含 #3251 的多微前端 HMR runtime 隔离)并解决冲突:保留按 chunk loading global 动态生成 listener global,同时保留本 PR 的 dev/watch/hot gating 与 scoped HMR。合并后再次验证 Pack 76/76、Chromium HMR E2E 8/8、@utoo/web tsc、@utoo/pack 本地 binding build 均通过;PR 已恢复可合并,最新 CI 已触发。

…nt-scoping

# Conflicts:
#	packages/pack/src/commands/build.ts
#	packages/pack/src/core/hmr.ts
#	packages/pack/src/utils/env.ts
@github-actions

Copy link
Copy Markdown

📊 Performance Benchmark Report (with-antd)

Utoopack Performance Report

Report ID: utoopack_performance_report_20260722_040416
Generated: 2026-07-22 04:04:16
Trace File: trace_antd.json (0.4GB, 1.13M spans)
Test Project: examples/with-antd


Executive Summary

Metric Value Assessment
Total Wall Time 6,790.7 ms Baseline
Total Thread Work (de-duped) 19,436.8 ms Non-overlapping busy time
Effective Parallelism 2.9x thread_work / wall_time
Working Threads 10 Threads with actual spans
Thread Utilization 28.6% ⚠️ Suboptimal
Total Spans 1,127,093 All B/E + X events
Meaningful Spans (>= 10us) 306,335 (27.2% of total)
Tracing Noise (< 10us) 820,758 (72.8% of total)

Build Phase Timeline

Shows when each build phase is active and how much CPU it consumes.
Self-Time is the time spent exclusively in that phase (excluding children).

Phase Spans Inclusive (ms) Self-Time (ms) Wall Range (ms)
Resolve 78,179 2,163.4 1,684.7 3,334.2
Parse 9,003 1,141.2 1,066.4 6,198.6
Analyze 196,206 11,289.7 7,827.4 6,088.0
Chunk 7,634 2,830.5 790.8 2,468.2
Codegen 9,966 1,467.3 1,386.3 2,134.4
Emit 25 49.7 25.9 9.5
Other 5,322 6,348.9 3,836.5 6,790.7

Workload Distribution by Diagnostic Tier

Category Spans Inclusive (ms) % Work Self-Time (ms) % Self
P0: Scheduling & Resolution 276,743 13,843.1 71.2% 9,772.7 50.3%
P1: I/O & Heavy Tasks 2,883 133.1 0.7% 109.3 0.6%
P2: Architecture (Locks/Memory) 0 0.0 0.0% 0.0 0.0%
P3: Asset Pipeline 25,008 5,445.7 28.0% 3,250.2 16.7%
P4: Bridge/Interop 0 0.0 0.0% 0.0 0.0%
Other 1,701 5,868.7 30.2% 3,485.7 17.9%

Top 20 Tasks by Self-Time

Self-time is the exclusive duration: time spent in the task itself, not in sub-tasks.
This is the most accurate indicator of where CPU cycles are actually spent.

Self (ms) Inclusive (ms) Count Avg Self (us) P95 Self (ms) Max Self (ms) % Work Task Name Top Caller
4,110.4 5,997.8 123,743 33.2 0.1 12.6 21.1% module write all entrypoints to disk (1%)
2,344.9 2,428.5 25 93794.2 366.7 501.3 12.1% save snapshot persist (4%)
1,839.2 1,923.2 23,887 77.0 0.2 172.0 9.5% analyze ecmascript module module (64%)
1,036.0 1,127.9 42,770 24.2 0.0 4.8 5.3% internal resolving resolving (30%)
993.4 2,458.1 40,857 24.3 0.0 6.5 5.1% process module module (18%)
991.6 1,066.4 6,830 145.2 0.6 65.3 5.1% parse ecmascript process module (28%)
738.5 738.5 5,252 140.6 0.5 117.2 3.8% compute async module info None (0%)
723.4 723.4 7,742 93.4 0.4 6.0 3.7% precompute code generation generate merged code (52%)
679.1 2,718.0 6,379 106.5 0.1 50.2 3.5% chunking compute async chunks (0%)
660.1 1,989.9 1,425 463.2 1.2 263.7 3.4% generate merged code chunking (67%)
640.0 1,026.9 34,724 18.4 0.0 4.9 3.3% resolving module (27%)
453.9 453.9 329 1379.7 1.6 287.4 2.3% generate source map code generation (83%)
366.3 767.5 173 2117.5 4.0 224.6 1.9% emit code generate merged code (32%)
335.2 461.0 3,289 101.9 0.0 182.7 1.7% write all entrypoints to disk None (0%)
209.0 290.0 1,895 110.3 0.2 46.7 1.1% code generation chunking (13%)
108.1 109.0 1,199 90.2 0.2 8.3 0.6% compute async chunks write all entrypoints to disk (0%)
92.9 92.9 14 6632.9 32.2 54.0 0.5% blocking save snapshot (64%)
81.6 81.6 578 141.1 0.0 23.5 0.4% compute binding usage info write all entrypoints to disk (1%)
74.7 74.7 2,167 34.5 0.0 3.3 0.4% read file parse ecmascript (91%)
48.4 48.4 1,870 25.9 0.0 6.2 0.2% collect mergeable modules compute merged modules (0%)

Critical Path Analysis

The longest sequential dependency chains that determine wall-clock time.
Focus on reducing the depth of these chains to improve parallelism.

Rank Self-Time (ms) Depth Path
1 555.4 3 persist → save snapshot → blocking
2 488.3 3 chunking → generate merged code → emit code
3 367.3 2 save snapshot → blocking
4 342.8 4 chunking → generate merged code → emit code → generate source map
5 172.0 2 process module → analyze ecmascript module

Batching Candidates

High-volume tasks dominated by a single parent. If the parent can batch them,
it drastically reduces scheduler overhead.

Task Name Count Top Caller (Attribution) Avg Self P95 Self Total Self
No obvious batching candidates found - - - - -

Duration Distribution

Range Count Percentage
<10us 820,758 72.8%
10us-100us 288,241 25.6%
100us-1ms 14,859 1.3%
1ms-10ms 3,125 0.3%
10ms-100ms 90 0.0%
>100ms 20 0.0%

Action Items

  1. [P0] Focus on tasks with the highest Self-Time — these are where CPU cycles are actually spent.
  2. [P0] Use Batching Candidates to identify callers that should use try_join or reduce #[turbo_tasks::function] granularity.
  3. [P1] Check Build Phase Timeline for phases with disproportionate wall range vs. self-time (= serialization).
  4. [P1] Inspect P95 Self (ms) for heavy monolith tasks. Focus on long-tail outliers, not averages.
  5. [P1] Review Critical Paths — reducing the longest chain depth directly improves wall-clock time.
  6. [P2] If Thread Utilization < 60%, investigate scheduling gaps (lock contention or deep dependency chains).

Report generated by Utoopack Performance Analysis Agent

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