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
  • HMR browser regression suite passed (9/9); 54 targeted HMR, WebSocket, environment, and stats tests 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 已触发。

@fireairforce
fireairforce force-pushed the zoomdong/fix-hmr-client-scoping branch from e01456d to 49ed722 Compare July 24, 2026 02:59
@github-actions

Copy link
Copy Markdown

📊 Performance Benchmark Report (with-antd)

Utoopack Performance Report

Report ID: utoopack_performance_report_20260724_112319
Generated: 2026-07-24 11:23:19
Trace File: trace_antd.json (0.4GB, 1.13M spans)
Test Project: examples/with-antd


Executive Summary

Metric Value Assessment
Total Wall Time 6,701.9 ms Baseline
Total Thread Work (de-duped) 19,473.3 ms Non-overlapping busy time
Effective Parallelism 2.9x thread_work / wall_time
Working Threads 9 Threads with actual spans
Thread Utilization 32.3% ⚠️ Suboptimal
Total Spans 1,130,599 All B/E + X events
Meaningful Spans (>= 10us) 303,448 (26.8% of total)
Tracing Noise (< 10us) 827,151 (73.2% 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 77,537 2,221.4 1,722.3 3,292.4
Parse 9,005 1,126.2 1,063.7 6,052.3
Analyze 194,911 11,017.9 7,607.3 5,951.6
Chunk 7,520 2,991.2 805.4 2,398.9
Codegen 9,169 1,507.3 1,439.8 2,065.2
Emit 25 41.8 19.3 8.8
Other 5,281 6,735.6 3,978.1 6,701.9

Workload Distribution by Diagnostic Tier

Category Spans Inclusive (ms) % Work Self-Time (ms) % Self
P0: Scheduling & Resolution 274,734 13,586.4 69.8% 9,558.3 49.1%
P1: I/O & Heavy Tasks 2,879 114.5 0.6% 91.9 0.5%
P2: Architecture (Locks/Memory) 0 0.0 0.0% 0.0 0.0%
P3: Asset Pipeline 24,105 5,640.4 29.0% 3,324.6 17.1%
P4: Bridge/Interop 0 0.0 0.0% 0.0 0.0%
Other 1,730 6,300.1 32.4% 3,661.1 18.8%

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
3,929.1 5,768.9 123,210 31.9 0.1 9.7 20.2% module write all entrypoints to disk (1%)
2,425.7 2,541.7 25 97029.0 387.0 509.3 12.5% save snapshot persist (4%)
1,813.0 1,916.7 24,052 75.4 0.2 162.4 9.3% analyze ecmascript module module (65%)
1,094.9 1,176.0 42,609 25.7 0.0 6.1 5.6% internal resolving resolving (30%)
1,012.3 2,453.5 39,967 25.3 0.0 5.5 5.2% process module module (17%)
1,000.9 1,063.4 6,832 146.5 0.5 76.9 5.1% parse ecmascript process module (28%)
751.4 751.4 6,972 107.8 0.4 6.1 3.9% precompute code generation generate merged code (54%)
717.5 717.5 5,213 137.6 0.4 124.4 3.7% compute async module info compute merged modules (0%)
710.9 2,148.6 1,445 492.0 1.1 254.8 3.7% generate merged code chunking (70%)
695.6 2,880.4 6,393 108.8 0.1 53.9 3.6% chunking compute async chunks (0%)
617.6 1,035.6 34,247 18.0 0.0 3.5 3.2% resolving module (26%)
500.1 500.1 329 1520.1 1.4 301.2 2.6% generate source map code generation (83%)
379.3 834.8 171 2218.2 4.2 215.4 1.9% emit code generate merged code (32%)
287.6 395.6 3,187 90.2 0.0 150.0 1.5% write all entrypoints to disk None (0%)
188.2 255.8 1,868 100.8 0.2 39.0 1.0% code generation chunking (12%)
119.6 119.6 15 7973.7 55.2 56.6 0.6% blocking save snapshot (60%)
106.8 107.8 1,081 98.8 0.2 8.6 0.5% compute async chunks compute async chunks (0%)
78.5 78.5 584 134.3 0.0 24.4 0.4% compute binding usage info write all entrypoints to disk (1%)
62.7 62.7 2,170 28.9 0.0 1.1 0.3% read file parse ecmascript (91%)
43.4 43.4 1,860 23.3 0.0 5.9 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 565.9 3 persist → save snapshot → blocking
2 470.2 3 chunking → generate merged code → emit code
3 391.1 2 save snapshot → blocking
4 360.2 4 chunking → generate merged code → emit code → generate source map
5 212.6 2 save snapshot → blocking

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 827,151 73.2%
10us-100us 285,491 25.3%
100us-1ms 14,847 1.3%
1ms-10ms 2,999 0.3%
10ms-100ms 91 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