Skip to content

[pull] canary from vercel:canary#1038

Merged
pull[bot] merged 3 commits into
code:canaryfrom
vercel:canary
May 11, 2026
Merged

[pull] canary from vercel:canary#1038
pull[bot] merged 3 commits into
code:canaryfrom
vercel:canary

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented May 11, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

lukesandberg and others added 3 commits May 10, 2026 22:39
#91790)

> **Note:** This is a **proof of concept** implementation. It is not yet
ready for production use.

## Summary

Implements memory eviction for the turbo-tasks engine. After a
persistence snapshot completes, tasks that are safe to remove are
evicted from in-memory storage and transparently restored from disk on
next access.

### Eviction levels

- **Full eviction**: Entire task removed from the in-memory map
(restored from disk on access). Only possible when the task has no
meaningful transient state (and other state is already on disk)
- **DataAndMeta eviction**: Both data and meta categories cleared, but
the task stays in the map to preserve transient state (e.g.
`current_session_clean`, aggregated session-clean counts).
- **DataOnly eviction**: Only data-category fields cleared; meta (graph
structure, output, dirty state) stays in memory.
- **MetaOnly eviction**: Only meta-category fields cleared; data stays
in memory.

Data and meta evictability are computed independently — if one category
is modified but the other is clean, the clean category can still be
dropped.

Eviction is gated behind `BackendOptions::evict_after_snapshot` (off by
default), and can be enabled in Next.js via the
`TURBO_ENGINE_EVICT_AFTER_SNAPSHOT=1` env var for testing.

## Key changes

- **Orthogonal eviction decision tree** (`storage_schema.rs`): Data and
meta evictability are computed independently. Full eviction additionally
requires no meaningful transient state (session-clean flags, aggregated
session-clean counts). Replaces the previous sequential bail-out
approach which was too aggressive on full eviction (losing transient
session state on leaf tasks) and not aggressive enough on partial
eviction (blocking all eviction when only one category was modified).

- **`drop_partial()` codegen** (`task_storage_macro.rs`): New generated
methods to drop data

- **`restore_from_*()` codegen changes** (`task_storage_macro.rs`): New
semantics for merging persistent data from the backend with transient
data stored in memory.

- **`task_cache` moved into `Storage`** (`storage.rs`): The
`CachedTaskType → TaskId` deduplication map was previously a separate
field on `TurboTasksBackendInner`. It is now owned by `Storage` so
eviction can remove entries when a task is fully evicted. Because
`task_cache` is a pure performance cache (entries are re-populated by
`task_by_type()` on miss once the task type is persisted to backing
storage), evicting entries is safe. After bulk eviction the map is
shrunk when it is less than half full.

- **Parallel shard eviction** (`storage.rs`): Eviction iterates all
storage shards in parallel after snapshot, applying the appropriate
eviction level per task. Each shard is shrunk after bulk eviction to
reclaim slack capacity.
- In principle this is O(N) work to scan, but because each pass drops
>98% of tasks there isn't wasted work and the logic is fast, taking
<100ms for even the largest applications.


## Design notes

- **SessionDependent tasks**: SessionDependent tasks can still be
evicted but if `current_session_clean` is set we prevent full eviction
to avoid rechecking. Within a session the file-watchers are responsible
for invalidations after setting `current_session_clean`.

## Known limitations (proof of concept)

- No LRU or access-frequency tracking — all eligible tasks are evicted
on every snapshot cycle
- No memory pressure feedback — eviction runs on a timer, not in
response to actual memory pressure
- Only runs after snapshotting which tends to be a high point in memory
- Future work will explore interleaving this logic with snapshotting to
trim the peak



<!-- NEXT_JS_LLM_PR -->
## Report Turbopack feature-usage telemetry

Turbopack never reported `NEXT_BUILD_FEATURE_USAGE` telemetry for production builds. This PR wires it up and fixes a correctness bug in how the counts were computed, then cleans up the API surface that carried them across the napi boundary.

### Changes

- **JS**: `turbopackBuild()` now records `EVENT_BUILD_FEATURE_USAGE` events after `writeAllEntrypointsToDisk` via a new `eventBuildFeatureUsageFromTurbopackDiagnostics` helper. Dev is out of scope — webpack's `TelemetryPlugin` is `!dev && isClient` too.
- **Rust**: aligned feature names with the JS `EventBuildFeatureUsage['featureName']` union — SWC triple is now `swc/target/<triple>`; dropped `persistentCaching` (redundant with `turbopackFileSystemCache`) and `turbotrace: false` (hardcoded).

### Correctness fix: count unique importers, not resolves

Previously feature-usage counts for module imports (`next/image`, `next/font/google`, …) were computed from a `BeforeResolvePlugin` that emitted one event per resolve. Turbopack caches resolves, so the emission fired at most **once per unique request** — the count was effectively `1` for every feature that was imported anywhere. Webpack's equivalent counts unique importing modules via `moduleGraph.getIncomingConnections(module).size`.

This PR replaces the resolve-plugin emission with a single whole-app module-graph traversal on `Project`. For each tracked feature, we accumulate the set of unique parent modules of each matching node (mirroring webpack's "unique origin modules" semantics). Fonts are matched against their synthesized `/target.css?…` virtual modules produced by the SWC font-loader transform — matching webpack's `FEATURE_MODULE_REGEXP_MAP` approach. Paths are matched via `phf_map!` tables in `next_telemetry.rs`.

### Incidental simplifications

While in here, the `Diagnostic` collectibles subsystem got right-sized and then removed entirely, since feature usage was its only consumer:

- `Project::project_feature_usage()` returns a structured `Vc<ProjectFeatureUsageSummary>` instead of emitting diagnostics. Surfaced to JS as a dedicated `project.featureUsage(): Promise<BuildFeatureUsage[]>` napi method, called once at build's end.
- `TurbopackResult<T>` loses its `diagnostics: BuildFeatureUsage[]` field — it's now just `{ result, issues }`. Every napi result type and ~10 construction sites are correspondingly simpler.
- Deleted `turbopack_core::diagnostics` entirely (`Diagnostic` trait, `DiagnosticExt`, `DiagnosticContextExt`, `CapturedDiagnostics`, `PlainBuildFeatureUsage`). Deleted `FeatureUsageTelemetry`, `ModuleFeatureReportResolvePlugin`, `get_diagnostics()` aggregation, the `feature_usage`/`diagnostics` fields on `AllWrittenEntrypointsWithIssues`/`OperationResult`/`EntrypointsWithIssues`/`WrittenEndpointWithIssues`/`HmrUpdateWithIssues`/`HmrChunkNamesWithIssues`/`EndpointIssuesAndDiags`/`WriteAnalyzeResult`, and the defensive `drop_collectibles::<Box<dyn Diagnostic>>()` scrub in `entrypoints_without_collectibles_operation`.

Feature-usage telemetry now flows as a plain return value end-to-end: `Project::project_feature_usage()` → napi `projectFeatureUsage()` → JS `project.featureUsage()` → `telemetry.record()`. No collectibles, no peeking, no emission-as-side-effect.

### Tests

Un-skipped four previously webpack-only integration tests in `test/integration/telemetry/test/config.test.ts`: `image/script/dynamic`, `next/legacy/image`, `transpilePackages`, and middleware options. All pass under Turbopack. The remaining three skipped tests (`swc` flags, `@vercel/og`, `useCache`) cover features Turbopack doesn't emit yet — left skipped with TODOs.

Added unit test for the helper at `packages/next/src/telemetry/events/build.test.ts`. Updated the Turbopack `next-rs-api` snapshot to reflect the new diagnostic shape.

<!-- NEXT_JS_LLM_PR -->
@pull pull Bot locked and limited conversation to collaborators May 11, 2026
@pull pull Bot added the ⤵️ pull label May 11, 2026
@pull pull Bot merged commit e8f8f49 into code:canary May 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant