mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
### 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]>
180 lines
5.6 KiB
Rust
180 lines
5.6 KiB
Rust
#![feature(arbitrary_self_types)]
|
|
#![feature(arbitrary_self_types_pointers)]
|
|
#![allow(clippy::needless_return)] // tokio macro-generated code doesn't respect this
|
|
|
|
use anyhow::Result;
|
|
use rustc_hash::{FxHashMap, FxHashSet};
|
|
use turbo_tasks::{OperationVc, ResolvedVc, State, Vc};
|
|
use turbo_tasks_testing::{Registration, register, run};
|
|
|
|
static REGISTRATION: Registration = register!();
|
|
|
|
#[turbo_tasks::value(transparent)]
|
|
struct Step(State<u32>);
|
|
|
|
#[turbo_tasks::function(operation)]
|
|
fn create_state_operation() -> Vc<Step> {
|
|
Step(State::new(0)).cell()
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_invalidation_map() {
|
|
run(®ISTRATION, || async {
|
|
let state_op = create_state_operation();
|
|
let state_vc = state_op.resolve().strongly_consistent().await?;
|
|
let state = state_op.read_strongly_consistent().await?;
|
|
state.set(1);
|
|
|
|
let map = create_map(state_vc);
|
|
let a = get_value(map, "a".to_string());
|
|
let b = get_value(map, "b".to_string());
|
|
let c = get_value(map, "c".to_string());
|
|
|
|
let a_ref = a.read_strongly_consistent().await?;
|
|
let b_ref = b.read_strongly_consistent().await?;
|
|
let c_ref = c.read_strongly_consistent().await?;
|
|
|
|
assert_eq!(a_ref.value, Some(1));
|
|
assert_eq!(b_ref.value, Some(2));
|
|
assert_eq!(c_ref.value, None);
|
|
|
|
state.set(2);
|
|
|
|
let a_ref2 = a.read_strongly_consistent().await?;
|
|
let b_ref2 = b.read_strongly_consistent().await?;
|
|
let c_ref2 = c.read_strongly_consistent().await?;
|
|
|
|
assert_eq!(a_ref2.value, Some(1));
|
|
assert_eq!(b_ref2.value, Some(22));
|
|
assert_eq!(c_ref2.value, None);
|
|
assert_eq!(a_ref.random, a_ref2.random);
|
|
assert_eq!(c_ref.random, c_ref2.random);
|
|
|
|
state.set(3);
|
|
|
|
let a_ref3 = a.read_strongly_consistent().await?;
|
|
let b_ref3 = b.read_strongly_consistent().await?;
|
|
let c_ref3 = c.read_strongly_consistent().await?;
|
|
|
|
assert_eq!(a_ref3.value, None);
|
|
assert_eq!(b_ref3.value, Some(22));
|
|
assert_eq!(c_ref3.value, Some(3));
|
|
assert_eq!(b_ref2.random, b_ref3.random);
|
|
|
|
anyhow::Ok(())
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[turbo_tasks::value(transparent, cell = "keyed")]
|
|
struct Map(FxHashMap<String, u32>);
|
|
|
|
#[turbo_tasks::function(operation)]
|
|
async fn create_map(step: ResolvedVc<Step>) -> Result<Vc<Map>> {
|
|
let step = step.await?;
|
|
let step_value = step.get();
|
|
|
|
Ok(Vc::cell(match *step_value {
|
|
1 => FxHashMap::from_iter([("a".to_string(), 1), ("b".to_string(), 2)]),
|
|
2 => FxHashMap::from_iter([("a".to_string(), 1), ("b".to_string(), 22)]),
|
|
3 => FxHashMap::from_iter([("c".to_string(), 3), ("b".to_string(), 22)]),
|
|
_ => FxHashMap::default(),
|
|
}))
|
|
}
|
|
|
|
#[turbo_tasks::value]
|
|
struct GetValueResult {
|
|
value: Option<u32>,
|
|
random: u32,
|
|
}
|
|
|
|
#[turbo_tasks::function(operation)]
|
|
async fn get_value(map: OperationVc<Map>, key: String) -> Result<Vc<GetValueResult>> {
|
|
let map = map.connect();
|
|
let value = map.get(&key).await?.as_deref().copied();
|
|
let random = rand::random::<u32>();
|
|
Ok(GetValueResult { value, random }.cell())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_invalidation_set() {
|
|
run(®ISTRATION, || async {
|
|
let state_op = create_state_operation();
|
|
let state_vc = state_op.resolve().strongly_consistent().await?;
|
|
let state = state_op.read_strongly_consistent().await?;
|
|
state.set(1);
|
|
|
|
let set = create_set(state_vc);
|
|
let a = has_value(set, "a".to_string());
|
|
let b = has_value(set, "b".to_string());
|
|
let c = has_value(set, "c".to_string());
|
|
|
|
let a_ref = a.read_strongly_consistent().await?;
|
|
let b_ref = b.read_strongly_consistent().await?;
|
|
let c_ref = c.read_strongly_consistent().await?;
|
|
|
|
assert!(a_ref.value);
|
|
assert!(b_ref.value);
|
|
assert!(!c_ref.value);
|
|
|
|
state.set(2);
|
|
|
|
let a_ref2 = a.read_strongly_consistent().await?;
|
|
let b_ref2 = b.read_strongly_consistent().await?;
|
|
let c_ref2 = c.read_strongly_consistent().await?;
|
|
|
|
assert!(a_ref2.value);
|
|
assert!(b_ref2.value);
|
|
assert!(!c_ref2.value);
|
|
assert_eq!(a_ref.random, a_ref2.random);
|
|
assert_eq!(b_ref.random, b_ref2.random);
|
|
assert_eq!(c_ref.random, c_ref2.random);
|
|
|
|
state.set(3);
|
|
|
|
let a_ref3 = a.read_strongly_consistent().await?;
|
|
let b_ref3 = b.read_strongly_consistent().await?;
|
|
let c_ref3 = c.read_strongly_consistent().await?;
|
|
|
|
assert!(!a_ref3.value);
|
|
assert!(b_ref3.value);
|
|
assert!(c_ref3.value);
|
|
assert_eq!(b_ref2.random, b_ref3.random);
|
|
|
|
anyhow::Ok(())
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[turbo_tasks::value(transparent, cell = "keyed")]
|
|
struct Set(FxHashSet<String>);
|
|
|
|
#[turbo_tasks::function(operation)]
|
|
async fn create_set(step: ResolvedVc<Step>) -> Result<Vc<Set>> {
|
|
let step = step.await?;
|
|
let step_value = step.get();
|
|
|
|
Ok(Vc::cell(match *step_value {
|
|
1 => FxHashSet::from_iter(["a".to_string(), "b".to_string()]),
|
|
2 => FxHashSet::from_iter(["e".to_string(), "a".to_string(), "b".to_string()]),
|
|
3 => FxHashSet::from_iter(["c".to_string(), "b".to_string()]),
|
|
_ => FxHashSet::default(),
|
|
}))
|
|
}
|
|
|
|
#[turbo_tasks::value]
|
|
struct HasValueResult {
|
|
value: bool,
|
|
random: u32,
|
|
}
|
|
|
|
#[turbo_tasks::function(operation)]
|
|
async fn has_value(set: OperationVc<Set>, key: String) -> Result<Vc<HasValueResult>> {
|
|
let set = set.connect();
|
|
let value = set.contains_key(&key).await?;
|
|
let random = rand::random::<u32>();
|
|
Ok(HasValueResult { value, random }.cell())
|
|
}
|