[pull] canary from vercel:canary#1038
Merged
pull[bot] merged 3 commits intocode:canaryfrom May 11, 2026
Merged
Conversation
#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 -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 : )