Skip to content

turbo-tasks-backend: anchor GC roots, gate on Data residency, wire up the app#95977

Draft
lukesandberg wants to merge 1 commit into
lukesandberg/gc-03-parent-count-collectionfrom
lukesandberg/gc-04-roots-gates-integration
Draft

turbo-tasks-backend: anchor GC roots, gate on Data residency, wire up the app#95977
lukesandberg wants to merge 1 commit into
lukesandberg/gc-03-parent-count-collectionfrom
lukesandberg/gc-04-roots-gates-integration

Conversation

@lukesandberg

@lukesandberg lukesandberg commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What

Makes GC correct and safe on a real application: anchors GC roots, refuses to collect tasks whose data isn't resident, and wires the anchoring into the app.

  • gc_root: a task created with no parent (parent == None) — spawned from outside the tracked graph (a turbo_tasks.run/NAPI entry, run_once, or OperationVc::connect outside a task) — is marked a GC root (a persisted meta flag) and is never collected. Without this, root operations reach parent_count == 0 once their transient creator finishes and GC collects them, cascading into their whole subgraph. Expands TaskFlags from u16 to u32 to make room for the flag.
  • Data-resident gate: gc_maybe_collectible also requires Data restored, so GC never selects a task whose type-bearing Data was evicted (which crashed at snapshot). Interim stop-gap with a TODO — the real fix is restore-Data-in-GC or moving the task-type-hash into Meta.
  • DetachedVc pins its OperationVc across the NAPI boundary; pinned tasks are partially evicted rather than forced fully resident.
  • VersionedContentMap: serialization = "skip" + evict = "never" + pin/unpin of the OperationVcs it stores, so the ops held in its State are anchored (they live outside the task graph and would otherwise be collected).

Why

The collection pass (PR below) is correct for graph-reachable tasks but over-collects anything held outside the tracked graph — the project container, per-request operations, and ops stashed in long-lived State. This PR anchors those.

Known tradeoff (interim)

gc_root is a permanent pin, so request-scoped roots (e.g. a fresh source-map/trace operation per navigation) accumulate uncollected within a session. This is the conservative correctness-over-reclamation choice; a real root lifecycle is a follow-up.

Verification

Backend GC suites (incl. gc_does_not_collect_parentless_root_task and the flag-layout tests), and a dev-app run with TURBO_ENGINE_GC enabled through navigation/HMR cycles — no crashes, dead leaves collected.

Stack

Top of the parent_count GC stack; depends on all PRs below it. This is the complete feature.

lukesandberg commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Tests Passed

Commit: bfd2a20

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Stats from current PR

🔴 1 regression, 1 improvement

Metric Canary PR Change Trend
Webpack Warm (First Request) 3.208s 3.361s 🔴 +153ms (+5%) ▁▁▁▃▁
node_modules Size 527 MB 516 MB 🟢 11.2 MB (-2%) █████
📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 813ms 813ms ▇▂▁▂▂
Cold (Ready in log) 776ms 785ms █▁▁▁▁
Cold (First Request) 1.311s 1.308s ▇▁▁▁▁
Warm (Listen) 812ms 811ms ▇▂▂▂▁
Warm (Ready in log) 780ms 772ms ▇▁▁▁▁
Warm (First Request) 1.290s 1.295s ▇▂▁▁▂
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 761ms 760ms ▂▂▂▂▂
Cold (Ready in log) 757ms 753ms ▁▁▁▁▁
Cold (First Request) 3.254s 3.229s ▁▁▁▄▁
Warm (Listen) 760ms 761ms ▂▂▁▂▂
Warm (Ready in log) 750ms 755ms ▁▁▁▂▁
Warm (First Request) 3.208s 3.361s 🔴 +153ms (+5%) ▁▁▁▃▁

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 5.027s 5.081s ▇▃▂▁▁
Cached Build 5.188s 5.158s ▇▃▁▁▁
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 24.413s 24.191s ▁▁▁▁▁
Cached Build 24.527s 24.432s ▁▁▁▁▁
node_modules Size 527 MB 516 MB 🟢 11.2 MB (-2%) █████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
03gmyxr7dukvs.js gzip 10.3 kB N/A -
040t1q17lo593.js gzip 8.77 kB N/A -
0bb0vtp8dwsez.js gzip 450 B N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0l0sr6gau5xjn.js gzip 3.53 kB N/A -
0nst2xc-t0y6v.js gzip 160 B N/A -
0nxqz3q165m2l.js gzip 220 B N/A -
0qr-1tnb7_hg5.js gzip 5.72 kB N/A -
0qtrl0m2m47zb.js gzip 155 B N/A -
0uilzvklme4ea.js gzip 8.76 kB N/A -
0z0y83mfx-l-h.js gzip 10 kB N/A -
1_dj4hrtb4p26.js gzip 7.4 kB N/A -
15ok8ydf274z0.js gzip 10.6 kB N/A -
16cqqcigykfea.js gzip 13.6 kB N/A -
17h18zxt1915l.js gzip 1.46 kB N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1fq1h7dxad4cv.js gzip 9.44 kB N/A -
1fym-adbk_69_.js gzip 13.1 kB N/A -
1gav7nfh4y8ln.js gzip 8.76 kB N/A -
1o34vh44ndvtu.js gzip 13.2 kB N/A -
2-u463-ditelp.js gzip 156 B N/A -
23rhpy3kbs-7u.js gzip 65.6 kB N/A -
2dvqqp3zqpnnq.js gzip 160 B N/A -
2guls-_0xkbp2.js gzip 153 B N/A -
2lcep9_t56iw5.js gzip 166 B N/A -
2rypqffktcxqh.js gzip 8.69 kB N/A -
2uqsizxtehgqf.js gzip 8.79 kB N/A -
2vzktdqwbijqc.js gzip 8.74 kB N/A -
2ydpbosxm0bih.js gzip 156 B N/A -
2zezpu-768ip-.js gzip 155 B N/A -
3_4kzn9ufgy-9.js gzip 45.2 kB N/A -
32874-2qs3sy7.js gzip 155 B N/A -
3a80mp1w6p7__.js gzip 153 B N/A -
3c244akvx_-be.js gzip 8.74 kB N/A -
3dmlemitx3ef3.js gzip 2.29 kB N/A -
3eqn7bfc1f1vc.js gzip 71.1 kB N/A -
3i1ah-rd8gyl6.js gzip 156 B N/A -
3kxmazxf0zw6v.js gzip 156 B N/A -
3za9ke3-tiboq.js gzip 153 B N/A -
4237vdj6eyslt.js gzip 8.7 kB N/A -
turbopack-0q..obj3.js gzip 3.8 kB N/A -
turbopack-0s..e00r.js gzip 3.79 kB N/A -
turbopack-10..kg69.js gzip 3.8 kB N/A -
turbopack-13..bkpr.js gzip 3.8 kB N/A -
turbopack-15..07mf.js gzip 3.8 kB N/A -
turbopack-1c..8d9x.js gzip 3.8 kB N/A -
turbopack-1g..5cux.js gzip 3.8 kB N/A -
turbopack-1i..0hfx.js gzip 3.8 kB N/A -
turbopack-20..mf5k.js gzip 3.8 kB N/A -
turbopack-2l..5n26.js gzip 3.8 kB N/A -
turbopack-2y..j_7q.js gzip 3.8 kB N/A -
turbopack-31..2tc1.js gzip 3.82 kB N/A -
turbopack-3f..g_b7.js gzip 3.8 kB N/A -
turbopack-3r..ik36.js gzip 3.8 kB N/A -
0_brf1o8xtfgw.js gzip N/A 156 B -
00tc16j5-eos9.js gzip N/A 1.46 kB -
0gaw1gmpwuwkn.js gzip N/A 8.8 kB -
0lfd6n4hnvfus.js gzip N/A 156 B -
0mwo2e6a43qd6.js gzip N/A 154 B -
0oxxm8ra6x40w.js gzip N/A 155 B -
12zyjjmq4oad1.js gzip N/A 5.72 kB -
1b_5lm2rb0cxg.js gzip N/A 65.6 kB -
1k9cc4-gpa2cx.js gzip N/A 13.1 kB -
1kfiwrcr725kl.js gzip N/A 13.2 kB -
1n59uogejo81s.js gzip N/A 10.3 kB -
1npei3hlexdv3.js gzip N/A 9.99 kB -
1q-0qyccjh4q3.js gzip N/A 158 B -
1ssuiwj_rnpc5.js gzip N/A 8.77 kB -
2-phc9obd3nyd.js gzip N/A 8.77 kB -
20z9kb96_57f4.js gzip N/A 164 B -
24utq57r3l1cw.js gzip N/A 8.7 kB -
29tie9lu2une4.js gzip N/A 71 kB -
2aw0kxkhyra0_.js gzip N/A 153 B -
2eemzo7z71a10.js gzip N/A 154 B -
2fhjq6ek3srmr.js gzip N/A 8.69 kB -
2g0t7upz_v-hf.js gzip N/A 10.6 kB -
2ia9qqy99aie9.js gzip N/A 13.6 kB -
2n5fp99a5xt9s.js gzip N/A 7.4 kB -
2oza28kdqvoay.js gzip N/A 8.74 kB -
2s1ku9sx60j5g.js gzip N/A 155 B -
2smk18o7qzte6.js gzip N/A 154 B -
2zh8-mmhujy8o.js gzip N/A 2.29 kB -
30h_m0irj-q6p.js gzip N/A 221 B -
37ejbfx1u9lpo.js gzip N/A 8.75 kB -
38c63ct4i1cy7.js gzip N/A 449 B -
38pfb-vjc58zl.js gzip N/A 160 B -
3jimpal3lvz5e.js gzip N/A 9.44 kB -
3q86bccmeq9ht.js gzip N/A 45.1 kB -
3su6a4l8jbfth.js gzip N/A 3.53 kB -
3vp18tbs9ie7b.js gzip N/A 156 B -
3wb4pe4z4-aim.js gzip N/A 8.77 kB -
43cuc9gzge2dj.js gzip N/A 151 B -
turbopack-0c..ceu6.js gzip N/A 3.76 kB -
turbopack-0r..o21k.js gzip N/A 3.77 kB -
turbopack-0v..6n4f.js gzip N/A 3.78 kB -
turbopack-12..bfke.js gzip N/A 3.77 kB -
turbopack-1a..ll78.js gzip N/A 3.77 kB -
turbopack-1j..lqtq.js gzip N/A 3.77 kB -
turbopack-1v..tzol.js gzip N/A 3.79 kB -
turbopack-1x..lsi3.js gzip N/A 3.78 kB -
turbopack-2i..bzur.js gzip N/A 3.77 kB -
turbopack-3_..wlef.js gzip N/A 3.77 kB -
turbopack-3-..hdvm.js gzip N/A 3.77 kB -
turbopack-3l..eco6.js gzip N/A 3.77 kB -
turbopack-3m..txtb.js gzip N/A 3.77 kB -
turbopack-3w..c3ex.js gzip N/A 3.78 kB -
Total 448 kB 447 kB ✅ -421 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 778 B 749 B 🟢 29 B (-4%)
Total 778 B 749 B ✅ -29 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 427 B 436 B 🔴 +9 B (+2%)
Total 427 B 436 B ⚠️ +9 B

📦 Webpack

Client

Main Bundles
Canary PR Change
2486.HASH.js gzip 169 B N/A -
3146-HASH.js gzip 64.2 kB N/A -
39fcf99b-HASH.js gzip 62.9 kB N/A -
8443-HASH.js gzip 4.68 kB N/A -
9431-HASH.js gzip 5.62 kB N/A -
framework-HASH.js gzip 59.8 kB 59.8 kB
main-app-HASH.js gzip 255 B 254 B
main-HASH.js gzip 39.7 kB 40.1 kB 🔴 +452 B (+1%)
webpack-HASH.js gzip 1.68 kB 1.68 kB
6105-HASH.js gzip N/A 5.63 kB -
764.HASH.js gzip N/A 169 B -
8898-HASH.js gzip N/A 63.5 kB -
9597-HASH.js gzip N/A 4.65 kB -
e1ccab69-HASH.js gzip N/A 62.9 kB -
Total 239 kB 239 kB ✅ -267 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 194 B
_error-HASH.js gzip 183 B 182 B
css-HASH.js gzip 335 B 335 B
dynamic-HASH.js gzip 1.8 kB 1.8 kB
edge-ssr-HASH.js gzip 255 B 254 B
head-HASH.js gzip 351 B 349 B
hooks-HASH.js gzip 384 B 384 B
image-HASH.js gzip 581 B 580 B
index-HASH.js gzip 259 B 259 B
link-HASH.js gzip 2.49 kB 2.49 kB
routerDirect..HASH.js gzip 319 B 319 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 315 B 313 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.96 kB 7.95 kB ✅ -8 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 128 kB 128 kB
page.js gzip 285 kB 285 kB
Total 413 kB 413 kB ✅ -406 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 615 B 615 B
middleware-r..fest.js gzip 156 B 155 B
middleware.js gzip 45.4 kB 45.3 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 47 kB 46.9 kB ✅ -143 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 720 B 720 B
Total 720 B 720 B
Build Cache
Canary PR Change
0.pack gzip 4.83 MB 4.81 MB 🟢 17.3 kB (0%)
index.pack gzip 118 kB 119 kB
index.pack.old gzip 119 kB 118 kB
Total 5.06 MB 5.05 MB ✅ -17 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 366 kB 364 kB
app-page-exp..prod.js gzip 202 kB 201 kB
app-page-tur...dev.js gzip 365 kB 363 kB
app-page-tur..prod.js gzip 202 kB 201 kB
app-page-tur...dev.js gzip 362 kB 360 kB
app-page-tur..prod.js gzip 200 kB 199 kB
app-page.run...dev.js gzip 362 kB 360 kB
app-page.run..prod.js gzip 200 kB 199 kB
app-route-ex...dev.js gzip 81.7 kB 81.5 kB
app-route-ex..prod.js gzip 55.6 kB 55.4 kB
app-route-tu...dev.js gzip 81.8 kB 81.5 kB
app-route-tu..prod.js gzip 55.6 kB 55.4 kB
app-route-tu...dev.js gzip 81.4 kB 81.1 kB
app-route-tu..prod.js gzip 55.4 kB 55.1 kB
app-route.ru...dev.js gzip 81.3 kB 81.1 kB
app-route.ru..prod.js gzip 55.3 kB 55.1 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 45.4 kB 45.2 kB
pages-api-tu..prod.js gzip 34.1 kB 33.9 kB
pages-api.ru...dev.js gzip 45.4 kB 45.2 kB
pages-api.ru..prod.js gzip 34.1 kB 33.9 kB
pages-turbo....dev.js gzip 54.9 kB 54.6 kB
pages-turbo...prod.js gzip 39.7 kB 39.6 kB
pages.runtim...dev.js gzip 54.8 kB 54.6 kB
pages.runtim..prod.js gzip 39.7 kB 39.5 kB
server.runti..prod.js gzip 67.8 kB 67.5 kB
use-cache-pr...dev.js gzip 71.6 kB 71.4 kB
use-cache-pr...dev.js gzip 71.6 kB 71.4 kB
use-cache-pr...dev.js gzip 69.9 kB 69.7 kB
use-cache-pr...dev.js gzip 69.9 kB 69.7 kB
Total 3.51 MB 3.49 MB ✅ -15.6 kB
📝 Changed Files (29 files)

Files with changes:

  • app-page-exp..ntime.dev.js
  • app-page-exp..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page.runtime.dev.js
  • app-page.runtime.prod.js
  • app-route-ex..ntime.dev.js
  • app-route-ex..time.prod.js
  • app-route-tu..ntime.dev.js
  • app-route-tu..time.prod.js
  • app-route-tu..ntime.dev.js
  • app-route-tu..time.prod.js
  • app-route.runtime.dev.js
  • app-route.ru..time.prod.js
  • pages-api-tu..ntime.dev.js
  • pages-api-tu..time.prod.js
  • pages-api.runtime.dev.js
  • pages-api.ru..time.prod.js
  • ... and 9 more
View diffs
app-page-exp..ntime.dev.js
failed to diff
app-page-exp..time.prod.js
failed to diff
app-page-tur..ntime.dev.js
failed to diff
app-page-tur..time.prod.js
failed to diff
app-page-tur..ntime.dev.js
failed to diff
app-page-tur..time.prod.js
failed to diff
app-page.runtime.dev.js
failed to diff
app-page.runtime.prod.js
failed to diff
app-route-ex..ntime.dev.js

Diff too large to display

app-route-ex..time.prod.js

Diff too large to display

app-route-tu..ntime.dev.js

Diff too large to display

app-route-tu..time.prod.js

Diff too large to display

app-route-tu..ntime.dev.js

Diff too large to display

app-route-tu..time.prod.js

Diff too large to display

app-route.runtime.dev.js

Diff too large to display

app-route.ru..time.prod.js

Diff too large to display

pages-api-tu..ntime.dev.js

Diff too large to display

pages-api-tu..time.prod.js

Diff too large to display

pages-api.runtime.dev.js

Diff too large to display

pages-api.ru..time.prod.js

Diff too large to display

pages-turbo...ntime.dev.js

Diff too large to display

pages-turbo...time.prod.js

Diff too large to display

pages.runtime.dev.js

Diff too large to display

pages.runtime.prod.js

Diff too large to display

server.runtime.prod.js

Diff too large to display

use-cache-pr..ntime.dev.js

Diff too large to display

use-cache-pr..ntime.dev.js

Diff too large to display

use-cache-pr..ntime.dev.js

Diff too large to display

use-cache-pr..ntime.dev.js

Diff too large to display

📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/bfd2a202d68e7e44050a9ff5515d5e1b9278ed0f/next

Commit: bfd2a20

@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-03-parent-count-collection branch from a6f5c07 to 5d4aadd Compare July 20, 2026 22:43
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-04-roots-gates-integration branch 2 times, most recently from 3edcf11 to f9cad98 Compare July 20, 2026 23:17
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-03-parent-count-collection branch from 5d4aadd to d370353 Compare July 20, 2026 23:18
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-04-roots-gates-integration branch from f9cad98 to 1d92599 Compare July 21, 2026 16:08
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-03-parent-count-collection branch from d370353 to 223e74f Compare July 21, 2026 16:08
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-04-roots-gates-integration branch from 1d92599 to 4096208 Compare July 21, 2026 16:29
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-03-parent-count-collection branch from 223e74f to 8bc08cb Compare July 21, 2026 16:29
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-04-roots-gates-integration branch from 4096208 to d5b351e Compare July 21, 2026 16:41
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-03-parent-count-collection branch from 8bc08cb to 388dd36 Compare July 21, 2026 16:41
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-04-roots-gates-integration branch from d5b351e to f6bf401 Compare July 21, 2026 16:46
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-03-parent-count-collection branch from 388dd36 to 2839c72 Compare July 21, 2026 16:46
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-04-roots-gates-integration branch from f6bf401 to 8c41757 Compare July 21, 2026 17:02
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-03-parent-count-collection branch 2 times, most recently from b9f23e7 to 0b657eb Compare July 21, 2026 17:50
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-04-roots-gates-integration branch 2 times, most recently from fce4e76 to 9e36679 Compare July 21, 2026 20:07
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-03-parent-count-collection branch from 0b657eb to b9a39db Compare July 21, 2026 20:07
… the app

Makes GC correct and safe on a real application.

- gc_root: a task created with no parent (parent == None) — spawned from outside the
  tracked graph (a turbo_tasks.run/NAPI entry, run_once, OperationVc::connect outside a
  task) — is marked a GC root (a persisted meta flag) and never collected. Without this,
  root operations reached parent_count 0 once their transient creator finished and GC
  collected them, cascading into their whole subgraph. Expands TaskFlags to u32 for the
  new flag.
- Data-resident gate: gc_maybe_collectible also requires Data restored, so GC never
  selects a task whose type-bearing Data was evicted (which crashed at snapshot). Stop-gap
  with a TODO (real fix: restore-Data-in-GC or type-hash-in-Meta).
- DetachedVc pins its OperationVc across the NAPI boundary; partial-evict pinned tasks
  rather than force full residence.
- VersionedContentMap: serialization = "skip" + evict = "never" + pin/unpin the
  OperationVcs it stores, so the ops held in its State are anchored.

Tests: gc_does_not_collect_parentless_root_task, the flag-layout tests, and the pinned/
root scenarios.
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-04-roots-gates-integration branch from 9e36679 to bfd2a20 Compare July 22, 2026 05:05
@lukesandberg
lukesandberg force-pushed the lukesandberg/gc-03-parent-count-collection branch from b9a39db to f844d18 Compare July 22, 2026 05:05
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