Skip to content

Commit f65b10a

Browse files
sokraclaude
andauthored
turbo-tasks: replace async resolve fns with custom Future types (ResolveRawVcFuture, ResolveVcFuture, ToResolvedVcFuture) (#91554)
### What? Replace the `async fn resolve()`, `async fn resolve_strongly_consistent()`, and `async fn to_resolved()` methods on `RawVc`, `Vc<T>`, and `OperationVc<T>` with hand-written custom `Future` implementations, following the existing `ReadRawVcFuture` pattern. New types: - **`ResolveRawVcFuture`** (`raw_vc.rs`) — core implementation, replaces `async fn resolve_inner()` - **`ResolveVcFuture<T>`** (`vc/mod.rs`) — typed wrapper over `ResolveRawVcFuture`, returned by `Vc::resolve()` - **`ResolveOperationVcFuture<T>`** (`vc/operation.rs`) — typed wrapper, returned by `OperationVc::resolve()` - **`ToResolvedVcFuture<T>`** (`vc/mod.rs`) — typed wrapper, returned by `Vc::to_resolved()` All new future types expose a `.strongly_consistent()` builder method, enabling `resolve_strongly_consistent()` to be replaced by `.resolve().strongly_consistent()` at call sites. `ReadRawVcFuture` is also updated to delegate its phase-1 resolve loop to `ResolveRawVcFuture` instead of duplicating the logic. `std::task::ready!` is used throughout to simplify poll implementations. Also adds `#[inline(never)]` to `ReadRawVcFuture::poll` and `ResolveRawVcFuture::poll` to avoid inlining large poll implementations into every await site. ### Why? Performance, binary size, and improved API ergonomics: - The hand-written `Future` pattern (already used by `ReadRawVcFuture`) gives the compiler more predictable, smaller code than the state machines generated for `async fn`. The `#[inline(never)]` attributes on `poll` prevent large poll bodies from being duplicated at every await site, which the async desugaring otherwise allows. - The new builder API (`.resolve().strongly_consistent()`) is more composable and removes the need for separate `_strongly_consistent` method variants, reducing the number of methods on `RawVc`/`Vc`/`OperationVc`. - Having `ReadRawVcFuture` delegate to `ResolveRawVcFuture` removes the duplicated resolve loop and ensures both paths stay in sync. ### How? - `ResolveRawVcFuture` stores `current: RawVc`, `read_output_options: ReadOutputOptions`, `strongly_consistent: bool`, and `listener: Option<EventListener>`. Its `poll` replicates the loop from the old `resolve_inner` using `try_read_task_output` / `try_read_local_output`. - On `Err(listener)` from a `try_*` call, the listener is stored in `self.listener` and `Poll::Pending` is returned. At the top of the loop, `ready!(poll_listener(...))` re-polls it and short-circuits if still pending. - Consistency is downgraded to `Eventual` after the first `TaskOutput` hop, matching the previous behavior. - `strongly_consistent: true` keeps the `SUPPRESS_EVENTUAL_CONSISTENCY_TOP_LEVEL_TASK_CHECK` suppression across all polls (same logic as `ReadRawVcFuture`). - `ReadRawVcFuture` now holds a `ResolveRawVcFuture` for phase 1 and drives it via `Pin::new(&mut self.resolve).poll(cx)` before proceeding to the cell read in phase 2. This eliminates the duplicated loop that previously existed in both types. - Typed wrappers (`ResolveVcFuture<T>`, `ResolveOperationVcFuture<T>`, `ToResolvedVcFuture<T>`) delegate `poll` to the inner `ResolveRawVcFuture` and map the output to the appropriate typed result. - `OperationVc::resolve_strongly_consistent()` is removed; 16 call sites updated to `.resolve().strongly_consistent()`. - All new types implement `Unpin` and are exported from `lib.rs`. - `std::task::ready!` is used in all `poll` implementations to reduce boilerplate. No behavioral changes — this is a pure implementation refactor. ### Binary size impact A release build (`pnpm swc-build-native --release`) was measured before and after the branch changes on the same merge-base commit (`a41bef94`): | | Size | |---|---| | Base (`a41bef94`, before branch) | 199,690,656 bytes (~190.4 MB) | | Branch (`6f7846f9`, after changes) | 199,252,384 bytes (~190.0 MB) | | **Difference** | **−438,272 bytes (−428 KB, −0.22%)** | The branch produces a slightly smaller binary. The reduction comes primarily from the `#[inline(never)]` attributes preventing large `poll` bodies from being duplicated at every await site. --------- Co-authored-by: Tobias Koppers <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent f6663b7 commit f65b10a

25 files changed

Lines changed: 367 additions & 210 deletions

File tree

crates/next-api/src/module_graph.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl NextDynamicGraphs {
7676
// `get_global_information_for_endpoint_inner` calls `take_collectibles()` when needed
7777
let result_op = Self::new_operation(graphs, is_single_page);
7878
let result_vc = if !is_single_page {
79-
let result_vc = result_op.resolve_strongly_consistent().await?;
79+
let result_vc = result_op.resolve().strongly_consistent().await?;
8080
result_op.drop_collectibles::<Box<dyn Issue>>();
8181
*result_vc
8282
} else {
@@ -274,7 +274,7 @@ impl ServerActionsGraphs {
274274
// `get_global_information_for_endpoint_inner` calls `take_collectibles()` when needed
275275
let result_op = Self::new_operation(graphs, is_single_page);
276276
let result_vc = if !is_single_page {
277-
let result_vc = result_op.resolve_strongly_consistent().await?;
277+
let result_vc = result_op.resolve().strongly_consistent().await?;
278278
result_op.drop_collectibles::<Box<dyn Issue>>();
279279
*result_vc
280280
} else {
@@ -452,7 +452,7 @@ impl ClientReferencesGraphs {
452452
// `get_global_information_for_endpoint_inner` calls `take_collectibles()` when needed
453453
let result_op = Self::new_operation(graphs, is_single_page);
454454
let result_vc = if !is_single_page {
455-
let result_vc = result_op.resolve_strongly_consistent().await?;
455+
let result_vc = result_op.resolve().strongly_consistent().await?;
456456
result_op.drop_collectibles::<Box<dyn Issue>>();
457457
*result_vc
458458
} else {

crates/next-api/src/operation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub struct EntrypointsOperation {
3737
async fn entrypoints_without_collectibles_operation(
3838
entrypoints: OperationVc<Entrypoints>,
3939
) -> Result<Vc<Entrypoints>> {
40-
let _ = entrypoints.resolve_strongly_consistent().await?;
40+
let _ = entrypoints.resolve().strongly_consistent().await?;
4141
entrypoints.drop_collectibles::<Box<dyn Diagnostic>>();
4242
entrypoints.drop_issues();
4343
let _ = get_effects(entrypoints).await?;

crates/next-api/src/project.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,8 @@ impl ProjectContainer {
615615
container.connect().project()
616616
}
617617
let project = project_from_container_operation(this_op)
618-
.resolve_strongly_consistent()
618+
.resolve()
619+
.strongly_consistent()
619620
.await?;
620621
let project_fs = project_fs_operation(project)
621622
.read_strongly_consistent()
@@ -738,7 +739,8 @@ impl ProjectContainer {
738739
let watch = new_options.watch;
739740

740741
let project = project_operation(self)
741-
.resolve_strongly_consistent()
742+
.resolve()
743+
.strongly_consistent()
742744
.await?;
743745
let prev_project_fs = project_fs_operation(project)
744746
.read_strongly_consistent()
@@ -756,7 +758,8 @@ impl ProjectContainer {
756758
}
757759
this.options_state.set(Some(new_options));
758760
let project = project_operation(self)
759-
.resolve_strongly_consistent()
761+
.resolve()
762+
.strongly_consistent()
760763
.await?;
761764
let project_fs = project_fs_operation(project)
762765
.read_strongly_consistent()
@@ -1512,7 +1515,7 @@ impl Project {
15121515
} else {
15131516
// In development mode, we need to to take and drop the issues, otherwise every
15141517
// route will report all issues.
1515-
let vc = module_graphs_op.resolve_strongly_consistent().await?;
1518+
let vc = module_graphs_op.resolve().strongly_consistent().await?;
15161519
module_graphs_op.drop_issues();
15171520
*vc
15181521
};

crates/next-build-test/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub async fn main_inner(
4141
.run(async {
4242
let container_op = ProjectContainer::new_operation(rcstr!("next.js"), options.dev);
4343
ProjectContainer::initialize(container_op, options).await?;
44-
container_op.resolve_strongly_consistent().await
44+
container_op.resolve().strongly_consistent().await
4545
})
4646
.await?;
4747

@@ -248,7 +248,7 @@ async fn endpoint_write_to_disk_with_effects(
248248
endpoint: ResolvedVc<Box<dyn Endpoint>>,
249249
) -> Result<Vc<EndpointOutputPaths>> {
250250
let op = endpoint_write_to_disk_operation(endpoint);
251-
let result = op.resolve_strongly_consistent().await?;
251+
let result = op.resolve().strongly_consistent().await?;
252252
get_effects(op).await?.apply().await?;
253253
Ok(*result)
254254
}

crates/next-napi-bindings/src/next_api/project.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ pub fn project_new(
588588
.run(async move {
589589
let container_op = ProjectContainer::new_operation(rcstr!("next.js"), is_dev);
590590
ProjectContainer::initialize(container_op, options).await?;
591-
container_op.resolve_strongly_consistent().await
591+
container_op.resolve().strongly_consistent().await
592592
})
593593
.or_else(|e| turbopack_ctx.throw_turbopack_internal_result(&e.into()))
594594
.await?;

turbopack/crates/turbo-tasks-backend/tests/emptied_cells.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ static REGISTRATION: Registration = register!();
1212
async fn test_emptied_cells() {
1313
run(&REGISTRATION, || async {
1414
let input_op = get_state_operation();
15-
let input_vc = input_op.resolve_strongly_consistent().await?;
15+
let input_vc = input_op.resolve().strongly_consistent().await?;
1616
let input = input_op.read_strongly_consistent().await?;
1717
input.state.set(0);
1818

turbopack/crates/turbo-tasks-backend/tests/emptied_cells_session_dependent.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ static REGISTRATION: Registration = register!();
1212
async fn test_emptied_cells_session_dependent() {
1313
run(&REGISTRATION, || async {
1414
let input_op = get_state_operation();
15-
let input_vc = input_op.resolve_strongly_consistent().await?;
15+
let input_vc = input_op.resolve().strongly_consistent().await?;
1616
let input = input_op.read_strongly_consistent().await?;
1717
input.state.set(0);
1818

turbopack/crates/turbo-tasks-backend/tests/invalidation.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ fn create_state_operation() -> Vc<Step> {
2121
async fn test_invalidation_map() {
2222
run(&REGISTRATION, || async {
2323
let state_op = create_state_operation();
24-
let state_vc = state_op.resolve_strongly_consistent().await?;
24+
let state_vc = state_op.resolve().strongly_consistent().await?;
2525
let state = state_op.read_strongly_consistent().await?;
2626
state.set(1);
2727

@@ -101,7 +101,7 @@ async fn get_value(map: OperationVc<Map>, key: String) -> Result<Vc<GetValueResu
101101
async fn test_invalidation_set() {
102102
run(&REGISTRATION, || async {
103103
let state_op = create_state_operation();
104-
let state_vc = state_op.resolve_strongly_consistent().await?;
104+
let state_vc = state_op.resolve().strongly_consistent().await?;
105105
let state = state_op.read_strongly_consistent().await?;
106106
state.set(1);
107107

turbopack/crates/turbo-tasks-backend/tests/random_change.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ static REGISTRATION: Registration = register!();
1313
async fn test_random_change() {
1414
run_once(&REGISTRATION, || async {
1515
let state_op = make_state_operation();
16-
let state_vc = state_op.resolve_strongly_consistent().await?;
16+
let state_vc = state_op.resolve().strongly_consistent().await?;
1717
let state = state_op.read_strongly_consistent().await?;
1818

1919
let mut rng = StdRng::from_seed(Default::default());

turbopack/crates/turbo-tasks-backend/tests/top_level_task_consistency.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ async fn test_eventual_read_in_top_level_task_fails() {
3939
async fn test_cell_read_in_top_level_task_succeeds() {
4040
run_once(&REGISTRATION, || async {
4141
let cell = returns_value_operation()
42-
.resolve_strongly_consistent()
42+
.resolve()
43+
.strongly_consistent()
4344
.await?;
4445
let value = cell.await?;
4546
assert_eq!(value.value, 42);

0 commit comments

Comments
 (0)