Skip to content

turbo-tasks: add lazy cell reads via LazyReadRef - #101008

Draft
lukesandberg wants to merge 2 commits into
fleet/turbo-tasks-keyed-read-reffrom
fleet/turbo-tasks-lazy-read-ref
Draft

lukesandberg wants to merge 2 commits into
fleet/turbo-tasks-keyed-read-reffrom
fleet/turbo-tasks-lazy-read-ref

Conversation

@lukesandberg

@lukesandberg lukesandberg commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

What?

Adds vc.lazy_reader().await?, which returns a LazyReadRef<T> for any value type. The value is only accessible through an explicit LazyReadRef::get().

  • While the ref is alive, the task depends on the whole cell, so invalidations during the execution are not lost.
  • If the ref is dropped without get() ever being called, the dependency registered by this read is removed. That proves the task doesn't depend on the cell.
  • If get() was called, the dependency is kept and becomes a normal full dependency when the execution completes.

Stacked on #100884 and reuses its CellKey::Keyed(id) machinery.

Why?

Some tasks need a cell's value only in rare cases, and only find out in synchronous code. One example is the server actions hash salt (encryption key) in #100950: today that needs an optimistic "run without it, bail, re-run with it" strategy. With lazy_reader(), the task can read the cell up front, pass the ref into synchronous code, and depend on it only when it is actually used.

How?

  • One dependency per read: every lazy_reader() call gets its own CellKey::Keyed(id) dependency edge, the same per-execution id that keyed() uses.
    • Dropping an unaccessed ref calls convert_keyed_cell_dependency with no key hashes, which removes only that edge.
    • Full reads (.await), hashed/keyed reads (get, contains_key, keyed()), and other LazyReadRefs of the same cell keep their own edges. Mixed reads in one execution therefore behave correctly, in any order.
    • An accessed ref leaves its Keyed edge in place. The existing completion-time conversion turns it into a normal full dependency.
  • Lifetime rules: the same as KeyedReadRef, sharing the alive-ref counter. A ref that is leaked or dropped outside the creating execution fails the task with a panic error, and its dependency is kept as a full dependency.
    • In debug builds, a cross-execution poll, get() or drop also panics at the misuse site.
    • In release builds, the conservative full dependency is kept.
  • Reused race handling: the missing-content guard in convert_keyed_cell_dependency and the update_cell handling from turbo-tasks: add batched keyed reads via KeyedReadRef #100884 also apply to removal.
  • Vc-only API: lazy_reader() and keyed() exist only on Vc (so also on ResolvedVc via deref, and on OperationVc::connect()), not on the ReadVcFuture builder. vc.untracked().lazy_reader() / vc.untracked().keyed() don't make sense, since there is no dependency to cancel or narrow, so they are compile errors. Previously vc.untracked().keyed() from turbo-tasks: add batched keyed reads via KeyedReadRef #100884 compiled and silently became a tracked read.
  • No cost for other tasks: tasks that use neither keyed() nor lazy_reader() don't pay anything new. The completion scan and leak check stay gated on the existing per-execution counter.
  • keyed() and lazy_reader() share one read helper. keyed() behaviour is unchanged.
    • The only exception is the leak panic message, which now names both KeyedReadRef and LazyReadRef.
  • The new rule is documented in turbopack/AGENTS.md next to the keyed() guidance.

Adopting this in the server actions transform is a separate follow-up.

Verification

  • New turbopack/crates/turbo-tasks-backend/tests/lazy_read_ref.rs (16 tests) covers:
    • unaccessed vs accessed reads, and a cell change while the ref is alive;
    • two lazy refs (one or none accessed, both drop orders);
    • an unaccessed lazy read combined with a full read before or while the ref is alive;
    • lazy plus keyed get() and lazy plus batched keyed() reads;
    • access flipping between executions;
    • drops in a detached future;
    • leaked or cross-thread refs failing the task while keeping the dependency;
    • cross-execution misuse panicking in debug only;
    • Debug not counting as an access;
    • reads outside a task.
  • cargo test -p turbo-tasks -p turbo-tasks-backend passes, including the existing keyed_read_ref.rs.
  • cargo test --release -p turbo-tasks-backend --test lazy_read_ref --test keyed_read_ref passes, covering the release-mode fallback paths.
  • Mutation checks, each reverted afterwards:
    • keeping unaccessed dependencies → 5 failures;
    • removing accessed dependencies → 4 failures;
    • removing other full edges of the same cell → 3 failures.
  • Checked locally with temporary trybuild cases (not committed): vc.untracked().lazy_reader() and vc.untracked().keyed() fail with E0599 (no method on ReadVcFuture), while lazy_reader() / keyed() on Vc and ResolvedVc still compile.
  • cargo clippy -p turbo-tasks -p turbo-tasks-backend --all-targets -- -D warnings, cargo fmt --check, and RUSTDOCFLAGS=-Dwarnings cargo doc --no-deps --document-private-items -p turbo-tasks -p turbo-tasks-backend pass.
  • Not run locally: the workspace-wide ./scripts/check-rustdoc.sh, and Next.js e2e tests (no consumer is converted in this PR).
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Tests Passed

Commit: 3038a78

@github-actions github-actions Bot added created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js. labels Oct 31, 2026

@vercel vercel 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.

Vercel Agent reviewed 4dd4413 and found no issues.

Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>

@vercel vercel 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.

Vercel Agent reviewed 3038a78 and found no issues.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js.

1 participant