diff --git a/Cargo.lock b/Cargo.lock index 243dabd82acb..0d4d1f125481 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4765,6 +4765,7 @@ version = "0.1.0" dependencies = [ "next-core", "turbo-rcstr", + "turbo-tasks-backend", "turbopack-core", ] @@ -4847,6 +4848,7 @@ dependencies = [ "turbo-esregex", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-bytes", "turbo-tasks-env", "turbo-tasks-fetch", @@ -9735,6 +9737,7 @@ dependencies = [ "regex", "regress", "turbo-tasks", + "turbo-tasks-backend", ] [[package]] @@ -9850,6 +9853,7 @@ dependencies = [ "turbo-dyn-eq-hash", "turbo-frozenmap", "turbo-rcstr", + "turbo-tasks-backend", "turbo-tasks-hash", "turbo-tasks-macros", "turbo-tasks-malloc", @@ -9923,6 +9927,7 @@ dependencies = [ "futures", "serde", "turbo-tasks", + "turbo-tasks-backend", ] [[package]] @@ -9934,6 +9939,7 @@ dependencies = [ "turbo-bincode", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", ] @@ -10161,7 +10167,7 @@ dependencies = [ "tokio", "tungstenite 0.20.1", "turbo-tasks", - "turbo-tasks-testing", + "turbo-tasks-backend", "turbopack-create-test-app", "url", ] @@ -10182,6 +10188,7 @@ dependencies = [ "turbo-bincode", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbo-tasks-hash", "turbopack", @@ -10248,6 +10255,7 @@ dependencies = [ "serde", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbopack-core", "turbopack-resolve", @@ -10333,6 +10341,7 @@ dependencies = [ "turbo-bincode", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbopack-core", "turbopack-ecmascript", @@ -10434,6 +10443,7 @@ dependencies = [ "serde", "serde_json", "turbo-rcstr", + "turbo-tasks-backend", "turbopack-cli-utils", "turbopack-core", ] @@ -10459,6 +10469,7 @@ dependencies = [ "tracing", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbopack-core", "turbopack-ecmascript", @@ -10473,6 +10484,7 @@ dependencies = [ "serde", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbopack-core", "turbopack-ecmascript", @@ -10486,6 +10498,7 @@ dependencies = [ "async-trait", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-env", "turbo-tasks-fs", "turbopack-core", @@ -10508,6 +10521,7 @@ dependencies = [ "turbo-bincode", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbopack-core", ] @@ -10523,6 +10537,7 @@ dependencies = [ "serde", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbopack-core", "turbopack-ecmascript", @@ -10612,6 +10627,7 @@ dependencies = [ "turbo-bincode", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbo-tasks-hash", "turbopack", @@ -10635,6 +10651,7 @@ dependencies = [ "tracing", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbopack-core", ] @@ -10646,6 +10663,7 @@ dependencies = [ "anyhow", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbo-tasks-hash", "turbopack-core", @@ -10662,6 +10680,7 @@ dependencies = [ "swc_core", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbopack-core", ] @@ -10677,6 +10696,7 @@ dependencies = [ "similar", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbo-tasks-hash", "turbopack-cli-utils", @@ -10796,6 +10816,7 @@ dependencies = [ "serde", "turbo-rcstr", "turbo-tasks", + "turbo-tasks-backend", "turbo-tasks-fs", "turbo-tasks-hash", "turbopack-core", diff --git a/crates/next-build/Cargo.toml b/crates/next-build/Cargo.toml index b8fccb635dd8..7ae730886b35 100644 --- a/crates/next-build/Cargo.toml +++ b/crates/next-build/Cargo.toml @@ -17,3 +17,9 @@ next-core = { workspace = true } turbopack-core = { workspace = true } turbo-rcstr = { workspace = true } + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching +# `extern crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/crates/next-build/src/lib.rs b/crates/next-build/src/lib.rs index a324accba792..81f35c5769c7 100644 --- a/crates/next-build/src/lib.rs +++ b/crates/next-build/src/lib.rs @@ -1,6 +1,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub mod build_options; pub use self::build_options::BuildOptions; diff --git a/crates/next-core/Cargo.toml b/crates/next-core/Cargo.toml index 26cad2622132..fb8327363558 100644 --- a/crates/next-core/Cargo.toml +++ b/crates/next-core/Cargo.toml @@ -87,6 +87,12 @@ turbopack-resolve = { workspace = true } turbopack-static = { workspace = true } turbopack-trace-utils = { workspace = true } +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching `extern +# crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } + [features] default = ["process_pool"] process_pool = ["turbopack-node/process_pool"] diff --git a/crates/next-core/src/lib.rs b/crates/next-core/src/lib.rs index a157fe7521b4..3d3111296313 100644 --- a/crates/next-core/src/lib.rs +++ b/crates/next-core/src/lib.rs @@ -2,6 +2,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + mod app_page_loader_tree; pub mod app_structure; mod base_loader_tree; diff --git a/turbopack/crates/turbo-esregex/Cargo.toml b/turbopack/crates/turbo-esregex/Cargo.toml index 90dcff8d844e..a5b7d98fdd89 100644 --- a/turbopack/crates/turbo-esregex/Cargo.toml +++ b/turbopack/crates/turbo-esregex/Cargo.toml @@ -11,5 +11,13 @@ regex = { workspace = true } regress = { workspace = true } turbo-tasks = { workspace = true } +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols in `libturbo_tasks.rlib` resolve in this crate's test binary. +# Without something in this crate referencing `turbo-tasks-backend` from +# Rust, rustc wouldn't put its rlib in the link command. See the +# matching `extern crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } + [lints] workspace = true diff --git a/turbopack/crates/turbo-esregex/src/lib.rs b/turbopack/crates/turbo-esregex/src/lib.rs index 917ef282574a..71709134f6cd 100644 --- a/turbopack/crates/turbo-esregex/src/lib.rs +++ b/turbopack/crates/turbo-esregex/src/lib.rs @@ -1,5 +1,11 @@ #![feature(arbitrary_self_types_pointers)] +// Force the linker to pull in `turbo-tasks-backend`'s `__tt_static_*` +// providers for this crate's test binary. See the matching dev-dep in +// `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + use std::vec; use anyhow::{Result, bail}; diff --git a/turbopack/crates/turbo-tasks-backend/Cargo.toml b/turbopack/crates/turbo-tasks-backend/Cargo.toml index d9ffbfaf0cd8..cc0e65db23d0 100644 --- a/turbopack/crates/turbo-tasks-backend/Cargo.toml +++ b/turbopack/crates/turbo-tasks-backend/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [features] default = [] + print_cache_item_size_with_compressed = ["print_cache_item_size", "dep:lzzzz"] print_cache_item_size = [] no_fast_stale = [] diff --git a/turbopack/crates/turbo-tasks-backend/benches/scope_stress.rs b/turbopack/crates/turbo-tasks-backend/benches/scope_stress.rs index 4487e431afa5..acedc15a9a9f 100644 --- a/turbopack/crates/turbo-tasks-backend/benches/scope_stress.rs +++ b/turbopack/crates/turbo-tasks-backend/benches/scope_stress.rs @@ -38,6 +38,7 @@ pub fn scope_stress(c: &mut Criterion) { storage_mode: None, ..Default::default() }, + // Wrap to match `ProdBackingStorage`. noop_backing_storage(), )); async move { diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index f7ccd8bdfdce..157445ddb0c8 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -730,7 +730,7 @@ impl TurboTasksBackendInner { return result.map_err(|error| { self.task_error_to_turbo_tasks_execution_error(&error, &mut ctx) - .with_task_context(task_id, turbo_tasks.pin()) + .with_task_context(task_id, turbo_tasks::turbo_tasks()) .into() }); } diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs index afad86b812d0..92bffaf0d60d 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs @@ -19,8 +19,7 @@ use tracing::info_span; use tracing::trace_span; use turbo_tasks::{ CellId, DynTaskInputs, FxIndexMap, RawVc, SharedReference, TaskExecutionReason, TaskId, - TaskPriority, TurboTasks, TurboTasksCallApi, backend::CachedTaskType, - macro_helpers::NativeFunction, + TaskPriority, TurboTasks, backend::CachedTaskType, macro_helpers::NativeFunction, }; pub use self::aggregation_update::ComputeDirtyAndCleanUpdate; @@ -97,7 +96,7 @@ pub trait ExecuteContext<'e>: Sized { fn suspending_requested(&self) -> bool; fn should_track_dependencies(&self) -> bool; fn should_track_activeness(&self) -> bool; - fn turbo_tasks(&self) -> Arc; + fn turbo_tasks(&self) -> turbo_tasks::TurboTasksHandle; /// Look up a TaskId from the backing storage for a given task type. /// /// Uses hash-based lookup which may return multiple candidates due to hash collisions, @@ -976,8 +975,8 @@ impl<'e, B: BackingStorage> ExecuteContext<'e> for ExecuteContextImpl<'e, B> { self.backend.should_track_activeness() } - fn turbo_tasks(&self) -> Arc { - self.turbo_tasks.pin() + fn turbo_tasks(&self) -> turbo_tasks::TurboTasksHandle { + turbo_tasks::turbo_tasks() } fn task_by_type( diff --git a/turbopack/crates/turbo-tasks-backend/src/handle_providers.rs b/turbopack/crates/turbo-tasks-backend/src/handle_providers.rs new file mode 100644 index 000000000000..95e1cb461a84 --- /dev/null +++ b/turbopack/crates/turbo-tasks-backend/src/handle_providers.rs @@ -0,0 +1,212 @@ +//! `#[no_mangle] pub extern "Rust" fn __tt_static_*` providers for the +//! `TurboTasksHandle` dispatch. +//! +//! The forward declarations live in `turbo_tasks::handle`. Both sides +//! are unconditional — any binary that links `libturbo_tasks.rlib` +//! must also link `libturbo_tasks_backend.rlib` so the linker can +//! resolve the externs. +//! +//! Each provider: +//! 1. Casts the opaque `*const ()` receiver back to `&ProdHandleConcrete` (the production handle +//! type — `TurboTasks>`). +//! 2. Calls the trait method on the concrete type. +//! +//! Under thin LTO + `codegen-units = 1`, every step inlines into the +//! caller and the dispatch shape is `direct call` with no +//! indirect calls. See `turbo_tasks::handle` for the experiment that +//! verified this. + +use std::sync::Arc; + +use turbo_tasks::{TurboTasksApi as _, TurboTasksCallApi as _}; + +/// The concrete prod handle type. The `__tt_static_*` providers below +/// cast each opaque `*const ()` receiver to `&ProdHandleConcrete`, so +/// any `Arc>>` that goes into a +/// [`turbo_tasks::TurboTasksHandle`] via `make_handle` MUST be this +/// exact type. Mismatch is undefined behavior. See +/// [`crate::ProdBackingStorage`] for why the storage type is wrapped in +/// `Either`. +pub type ProdHandleConcrete = turbo_tasks::TurboTasks< + crate::TurboTasksBackend< + crate::KeyValueDatabaseBackingStorage, + >, +>; + +/// Generates `#[no_mangle] pub extern "Rust" fn __tt_static_(...)` +/// for a single dispatched method, dispatched via method call syntax. +macro_rules! provide_prod { + ( + fn $name:ident( $($arg:ident : $ty:ty),* $(,)? ) $(-> $ret:ty)? + ) => { + #[unsafe(no_mangle)] + pub extern "Rust" fn ${concat(__tt_static_, $name)}( + ptr: *const () + $(, $arg : $ty)* + ) $(-> $ret)? { + let tt: &ProdHandleConcrete = unsafe { &*(ptr as *const ProdHandleConcrete) }; + tt.$name($($arg),*) + } + }; +} + +// ---- dispatched methods --------------------------------------------------- +// +// Keep this list in sync with the matching `tt_decl!` invocations in +// `turbopack/crates/turbo-tasks/src/handle.rs`. + +// TurboTasksCallApi +provide_prod!(fn dynamic_call( + native_fn: &'static turbo_tasks::macro_helpers::NativeFunction, + this: Option, + arg: &mut dyn turbo_tasks::StackDynTaskInputs, + persistence: turbo_tasks::TaskPersistence, +) -> turbo_tasks::RawVc); +provide_prod!(fn native_call( + native_fn: &'static turbo_tasks::macro_helpers::NativeFunction, + this: Option, + arg: &mut dyn turbo_tasks::StackDynTaskInputs, + persistence: turbo_tasks::TaskPersistence, +) -> turbo_tasks::RawVc); +provide_prod!(fn trait_call( + trait_method: &'static turbo_tasks::TraitMethod, + this: turbo_tasks::RawVc, + arg: &mut dyn turbo_tasks::StackDynTaskInputs, + persistence: turbo_tasks::TaskPersistence, +) -> turbo_tasks::RawVc); +provide_prod!(fn send_compilation_event( + event: ::std::sync::Arc, +)); +provide_prod!(fn get_task_name(task: turbo_tasks::TaskId) -> ::std::string::String); + +// TurboTasksApi +provide_prod!(fn invalidate(task: turbo_tasks::TaskId)); +provide_prod!(fn invalidate_with_reason( + task: turbo_tasks::TaskId, + reason: turbo_tasks::util::StaticOrArc, +)); +provide_prod!(fn invalidate_serialization(task: turbo_tasks::TaskId)); +provide_prod!(fn try_read_task_output( + task: turbo_tasks::TaskId, + options: turbo_tasks::ReadOutputOptions, +) -> ::anyhow::Result<::core::result::Result>); +provide_prod!(fn try_read_task_cell( + task: turbo_tasks::TaskId, + index: turbo_tasks::CellId, + options: turbo_tasks::ReadCellOptions, +) -> ::anyhow::Result<::core::result::Result>); +provide_prod!(fn try_read_local_output( + execution_id: turbo_tasks::ExecutionId, + local_task_id: turbo_tasks::LocalTaskId, +) -> ::anyhow::Result<::core::result::Result>); +provide_prod!(fn read_task_collectibles( + task: turbo_tasks::TaskId, + trait_id: turbo_tasks::TraitTypeId, +) -> turbo_tasks::backend::TaskCollectiblesMap); +provide_prod!(fn emit_collectible( + trait_type: turbo_tasks::TraitTypeId, + collectible: turbo_tasks::RawVc, +)); +provide_prod!(fn unemit_collectible( + trait_type: turbo_tasks::TraitTypeId, + collectible: turbo_tasks::RawVc, + count: u32, +)); +provide_prod!(fn unemit_collectibles( + trait_type: turbo_tasks::TraitTypeId, + collectibles: &turbo_tasks::backend::TaskCollectiblesMap, +)); +provide_prod!(fn try_read_own_task_cell( + current_task: turbo_tasks::TaskId, + index: turbo_tasks::CellId, +) -> ::anyhow::Result); +provide_prod!(fn read_own_task_cell( + task: turbo_tasks::TaskId, + index: turbo_tasks::CellId, +) -> ::anyhow::Result); +provide_prod!(fn update_own_task_cell( + task: turbo_tasks::TaskId, + index: turbo_tasks::CellId, + content: turbo_tasks::backend::CellContent, + updated_key_hashes: ::core::option::Option<::smallvec::SmallVec<[u64; 2]>>, + content_hash: ::core::option::Option, + verification_mode: turbo_tasks::backend::VerificationMode, +)); +provide_prod!(fn mark_own_task_as_finished(task: turbo_tasks::TaskId)); +provide_prod!(fn connect_task(task: turbo_tasks::TaskId)); +provide_prod!(fn spawn_detached_for_testing( + f: ::std::pin::Pin<::std::boxed::Box + ::core::marker::Send + 'static>>, +)); +provide_prod!(fn is_tracking_dependencies() -> bool); + +// `task_statistics` is special: the trait method returns +// `&TaskStatisticsApi` borrowed from `&self`, but extern "Rust" can't +// carry that lifetime through a `*const ()` receiver. The provider +// returns a raw pointer; the handle wrapper in `turbo-tasks` re-binds +// the lifetime to `&self`. This is sound because the underlying Arc +// (held by the handle) keeps the `TaskStatisticsApi` alive. +#[unsafe(no_mangle)] +pub extern "Rust" fn __tt_static_task_statistics( + ptr: *const (), +) -> *const turbo_tasks::task_statistics::TaskStatisticsApi { + let tt: &ProdHandleConcrete = unsafe { &*(ptr as *const ProdHandleConcrete) }; + tt.task_statistics() as *const _ +} + +// ---- Arc clone / drop ----------------------------------------------------- + +#[unsafe(no_mangle)] +pub extern "Rust" fn __tt_static_clone_arc(ptr: *const ()) { + // Bump the refcount of the Arc whose data pointer is `ptr`. The caller + // (`::clone`) is responsible for reusing + // the same `ptr` value in the new handle, so we don't need to return + // anything. + unsafe { Arc::::increment_strong_count(ptr as *const ProdHandleConcrete) } +} + +#[unsafe(no_mangle)] +pub extern "Rust" fn __tt_static_drop_arc(ptr: *const ()) { + // Decrement the refcount; runs the destructor when it reaches zero. + unsafe { Arc::::decrement_strong_count(ptr as *const ProdHandleConcrete) } +} + +// ---- Weak refcount providers --------------------------------------------- + +#[unsafe(no_mangle)] +pub extern "Rust" fn __tt_static_downgrade(arc_ptr: *const ()) -> *const () { + // Reconstitute the Arc transiently to call `downgrade`, then leak the + // Arc back so its refcount is unchanged. The Weak we produce owns its + // own weak refcount. + let arc = unsafe { Arc::from_raw(arc_ptr as *const ProdHandleConcrete) }; + let weak = Arc::downgrade(&arc); + ::std::mem::forget(arc); + ::std::sync::Weak::into_raw(weak) as *const () +} + +#[unsafe(no_mangle)] +pub extern "Rust" fn __tt_static_upgrade(weak_ptr: *const ()) -> *const () { + // Reconstitute the Weak transiently to attempt upgrade, then leak it + // back so its refcount is unchanged. + let weak = unsafe { ::std::sync::Weak::from_raw(weak_ptr as *const ProdHandleConcrete) }; + let maybe_arc = weak.upgrade(); + ::std::mem::forget(weak); + match maybe_arc { + Some(arc) => Arc::into_raw(arc) as *const (), + None => ::std::ptr::null(), + } +} + +#[unsafe(no_mangle)] +pub extern "Rust" fn __tt_static_clone_weak(weak_ptr: *const ()) { + // `Weak` has no `increment_weak_count` API, so we round-trip through + // `Weak::clone` and leak both copies. + let weak = unsafe { ::std::sync::Weak::from_raw(weak_ptr as *const ProdHandleConcrete) }; + let cloned = weak.clone(); + ::std::mem::forget(weak); + ::std::mem::forget(cloned); +} + +#[unsafe(no_mangle)] +pub extern "Rust" fn __tt_static_drop_weak(weak_ptr: *const ()) { + drop(unsafe { ::std::sync::Weak::from_raw(weak_ptr as *const ProdHandleConcrete) }); +} diff --git a/turbopack/crates/turbo-tasks-backend/src/lib.rs b/turbopack/crates/turbo-tasks-backend/src/lib.rs index abd462881e07..4d3fece29d09 100644 --- a/turbopack/crates/turbo-tasks-backend/src/lib.rs +++ b/turbopack/crates/turbo-tasks-backend/src/lib.rs @@ -1,11 +1,13 @@ #![feature(anonymous_lifetime_in_impl_trait)] #![feature(box_patterns)] +#![feature(macro_metavar_expr_concat)] mod backend; mod backing_storage; mod data; mod database; mod error; +mod handle_providers; mod kv_backing_storage; mod utils; diff --git a/turbopack/crates/turbo-tasks-backend/tests/eviction.rs b/turbopack/crates/turbo-tasks-backend/tests/eviction.rs index 2fa4a7088a01..2ad6f564b887 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/eviction.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/eviction.rs @@ -81,36 +81,37 @@ async fn eviction_recompute() { let (tt, _persistence_dir) = create_tt("eviction_recompute"); let tt2 = tt.clone(); - let result = turbo_tasks::run_once(tt.clone(), async move { - unmark_top_level_task_may_leak_eventually_consistent_state(); - - // Create state via operation (persistent task) - let state_op = create_state(1); - let state_vc = state_op.resolve().strongly_consistent().await?; - let state = state_op.read_strongly_consistent().await?; - - // Create compute task (persistent, depends on state) - let output = compute(state_vc); - let read = output.read_strongly_consistent().await?; - assert_eq!(read.value, 1); - let initial_random = read.random; - - // Trigger snapshot + eviction - let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); - println!("snapshot had_data={had_data}, evicted: {counts:?}"); - assert!(had_data, "snapshot should have persisted data"); - - // Invalidate via state change — this requires restoring evicted tasks - state.set(2); - - // Read again — tasks must be restored from disk before re-executing - let read = output.read_strongly_consistent().await?; - assert_eq!(read.value, 2); - assert_ne!(read.random, initial_random); - - anyhow::Ok(()) - }) - .await; + let result = tt + .run_once(async move { + unmark_top_level_task_may_leak_eventually_consistent_state(); + + // Create state via operation (persistent task) + let state_op = create_state(1); + let state_vc = state_op.resolve().strongly_consistent().await?; + let state = state_op.read_strongly_consistent().await?; + + // Create compute task (persistent, depends on state) + let output = compute(state_vc); + let read = output.read_strongly_consistent().await?; + assert_eq!(read.value, 1); + let initial_random = read.random; + + // Trigger snapshot + eviction + let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); + println!("snapshot had_data={had_data}, evicted: {counts:?}"); + assert!(had_data, "snapshot should have persisted data"); + + // Invalidate via state change — this requires restoring evicted tasks + state.set(2); + + // Read again — tasks must be restored from disk before re-executing + let read = output.read_strongly_consistent().await?; + assert_eq!(read.value, 2); + assert_ne!(read.random, initial_random); + + anyhow::Ok(()) + }) + .await; tt.stop_and_wait().await; result.unwrap(); } @@ -123,51 +124,52 @@ async fn eviction_deep_chain() { let (tt, _persistence_dir) = create_tt("eviction_deep_chain"); let tt2 = tt.clone(); - let result = turbo_tasks::run_once(tt.clone(), async move { - unmark_top_level_task_may_leak_eventually_consistent_state(); - - let state_op = create_state(10); - let state_vc = state_op.resolve().strongly_consistent().await?; - let state = state_op.read_strongly_consistent().await?; - - let output = deep_chain(state_vc); - let read = output.read_strongly_consistent().await?; - // (10+1)*3+10 = 43 - assert_eq!(read.value, 43); - let initial_random = read.random; - - // Snapshot + evict — expect multiple intermediate tasks evicted - let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); - println!("deep_chain: snapshot had_data={had_data}, evicted: {counts:?}"); - assert!(had_data, "snapshot should have persisted data"); - assert!( - counts.full + counts.data_and_meta + counts.data_only + counts.meta_only > 0, - "expected some tasks to be evicted" - ); - - // Change the deepest input — must propagate through all restored tasks - state.set(20); - - let read = output.read_strongly_consistent().await?; - // (20+1)*3+10 = 73 - assert_eq!(read.value, 73); - assert_ne!(read.random, initial_random); - let random_after_first = read.random; - - // Evict again and change again - let (had_data2, counts2) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); - println!("deep_chain (2nd): snapshot had_data={had_data2}, evicted: {counts2:?}"); - - state.set(0); - - let read = output.read_strongly_consistent().await?; - // (0+1)*3+10 = 13 - assert_eq!(read.value, 13); - assert_ne!(read.random, random_after_first); - - anyhow::Ok(()) - }) - .await; + let result = tt + .run_once(async move { + unmark_top_level_task_may_leak_eventually_consistent_state(); + + let state_op = create_state(10); + let state_vc = state_op.resolve().strongly_consistent().await?; + let state = state_op.read_strongly_consistent().await?; + + let output = deep_chain(state_vc); + let read = output.read_strongly_consistent().await?; + // (10+1)*3+10 = 43 + assert_eq!(read.value, 43); + let initial_random = read.random; + + // Snapshot + evict — expect multiple intermediate tasks evicted + let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); + println!("deep_chain: snapshot had_data={had_data}, evicted: {counts:?}"); + assert!(had_data, "snapshot should have persisted data"); + assert!( + counts.full + counts.data_and_meta + counts.data_only + counts.meta_only > 0, + "expected some tasks to be evicted" + ); + + // Change the deepest input — must propagate through all restored tasks + state.set(20); + + let read = output.read_strongly_consistent().await?; + // (20+1)*3+10 = 73 + assert_eq!(read.value, 73); + assert_ne!(read.random, initial_random); + let random_after_first = read.random; + + // Evict again and change again + let (had_data2, counts2) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); + println!("deep_chain (2nd): snapshot had_data={had_data2}, evicted: {counts2:?}"); + + state.set(0); + + let read = output.read_strongly_consistent().await?; + // (0+1)*3+10 = 13 + assert_eq!(read.value, 13); + assert_ne!(read.random, random_after_first); + + anyhow::Ok(()) + }) + .await; tt.stop_and_wait().await; result.unwrap(); } @@ -180,49 +182,50 @@ async fn eviction_dependency_chain() { let (tt, _persistence_dir) = create_tt("eviction_dependency_chain"); let tt2 = tt.clone(); - let result = turbo_tasks::run_once(tt.clone(), async move { - unmark_top_level_task_may_leak_eventually_consistent_state(); - - let state_op = create_state(10); - let state_vc = state_op.resolve().strongly_consistent().await?; - let state = state_op.read_strongly_consistent().await?; - - let output = compute_chain(state_vc); - let read = output.read_strongly_consistent().await?; - assert_eq!(read.value, 20); // 10 * 2 - let initial_random = read.random; - - // Snapshot + evict - let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); - println!("snapshot had_data={had_data}, evicted: {counts:?}"); - assert!(had_data, "snapshot should have persisted data"); - assert!( - counts.full + counts.data_and_meta + counts.data_only + counts.meta_only > 0, - "expected some tasks to be evicted" - ); - - // Change the deepest input - state.set(5); - - let read = output.read_strongly_consistent().await?; - assert_eq!(read.value, 10); // 5 * 2 - assert_ne!(read.random, initial_random); - let random_after_first = read.random; - - // Evict again - let (had_data2, counts2) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); - println!("snapshot (2nd) had_data={had_data2}, evicted: {counts2:?}"); - - // Change again - state.set(100); - - let read = output.read_strongly_consistent().await?; - assert_eq!(read.value, 200); // 100 * 2 - assert_ne!(read.random, random_after_first); - - anyhow::Ok(()) - }) - .await; + let result = tt + .run_once(async move { + unmark_top_level_task_may_leak_eventually_consistent_state(); + + let state_op = create_state(10); + let state_vc = state_op.resolve().strongly_consistent().await?; + let state = state_op.read_strongly_consistent().await?; + + let output = compute_chain(state_vc); + let read = output.read_strongly_consistent().await?; + assert_eq!(read.value, 20); // 10 * 2 + let initial_random = read.random; + + // Snapshot + evict + let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); + println!("snapshot had_data={had_data}, evicted: {counts:?}"); + assert!(had_data, "snapshot should have persisted data"); + assert!( + counts.full + counts.data_and_meta + counts.data_only + counts.meta_only > 0, + "expected some tasks to be evicted" + ); + + // Change the deepest input + state.set(5); + + let read = output.read_strongly_consistent().await?; + assert_eq!(read.value, 10); // 5 * 2 + assert_ne!(read.random, initial_random); + let random_after_first = read.random; + + // Evict again + let (had_data2, counts2) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); + println!("snapshot (2nd) had_data={had_data2}, evicted: {counts2:?}"); + + // Change again + state.set(100); + + let read = output.read_strongly_consistent().await?; + assert_eq!(read.value, 200); // 100 * 2 + assert_ne!(read.random, random_after_first); + + anyhow::Ok(()) + }) + .await; tt.stop_and_wait().await; result.unwrap(); } @@ -358,44 +361,45 @@ async fn eviction_session_stateful_survives() { let (tt, _persistence_dir) = create_tt("eviction_session_stateful_survives"); let tt2 = tt.clone(); - let result = turbo_tasks::run_once(tt.clone(), async move { - unmark_top_level_task_may_leak_eventually_consistent_state(); - - // read_session_counter internally creates+resolves create_session_counter(42). - // The transient run_once only reads read_session_counter, so - // create_session_counter has no transient dependents and is eligible for - // eviction consideration — but should be blocked by SessionStateful. - let reader = read_session_counter(42); - let read = reader.read_strongly_consistent().await?; - assert_eq!(read.value, 42); - - // Also build a normal (evictable) chain for comparison - let state_op = create_state(10); - let state_vc = state_op.resolve().strongly_consistent().await?; - let normal = deep_chain(state_vc); - let normal_read = normal.read_strongly_consistent().await?; - // (10+1)*3+10 = 43 - assert_eq!(normal_read.value, 43); - - // Snapshot + evict - let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); - println!("session_stateful: snapshot had_data={had_data}, evicted: {counts:?}"); - assert!(had_data, "snapshot should have persisted data"); - // The normal intermediate tasks (add_one, times_three, plus_ten) should be - // evicted. The session-stateful create_session_counter should NOT be fully - // evicted (its data is blocked by has_session_stateful_cells). - assert!( - counts.full + counts.data_and_meta + counts.data_only + counts.meta_only > 0, - "normal intermediate tasks should be evicted" - ); - - // After eviction, reading through the session-stateful chain should still work - let read2 = reader.read_strongly_consistent().await?; - assert_eq!(read2.value, 42); - - anyhow::Ok(()) - }) - .await; + let result = tt + .run_once(async move { + unmark_top_level_task_may_leak_eventually_consistent_state(); + + // read_session_counter internally creates+resolves create_session_counter(42). + // The transient run_once only reads read_session_counter, so + // create_session_counter has no transient dependents and is eligible for + // eviction consideration — but should be blocked by SessionStateful. + let reader = read_session_counter(42); + let read = reader.read_strongly_consistent().await?; + assert_eq!(read.value, 42); + + // Also build a normal (evictable) chain for comparison + let state_op = create_state(10); + let state_vc = state_op.resolve().strongly_consistent().await?; + let normal = deep_chain(state_vc); + let normal_read = normal.read_strongly_consistent().await?; + // (10+1)*3+10 = 43 + assert_eq!(normal_read.value, 43); + + // Snapshot + evict + let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); + println!("session_stateful: snapshot had_data={had_data}, evicted: {counts:?}"); + assert!(had_data, "snapshot should have persisted data"); + // The normal intermediate tasks (add_one, times_three, plus_ten) should be + // evicted. The session-stateful create_session_counter should NOT be fully + // evicted (its data is blocked by has_session_stateful_cells). + assert!( + counts.full + counts.data_and_meta + counts.data_only + counts.meta_only > 0, + "normal intermediate tasks should be evicted" + ); + + // After eviction, reading through the session-stateful chain should still work + let read2 = reader.read_strongly_consistent().await?; + assert_eq!(read2.value, 42); + + anyhow::Ok(()) + }) + .await; tt.stop_and_wait().await; result.unwrap(); } @@ -411,50 +415,51 @@ async fn eviction_transient_reader_invalidated() { let (tt, _persistence_dir) = create_tt("eviction_transient_reader_invalidated"); let tt2 = tt.clone(); - let result = turbo_tasks::run_once(tt.clone(), async move { - unmark_top_level_task_may_leak_eventually_consistent_state(); - - // Create persistent state + compute tasks - let state_op = create_state(50); - let state_vc = state_op.resolve().strongly_consistent().await?; - let state = state_op.read_strongly_consistent().await?; - - let output = compute(state_vc); - let read = output.read_strongly_consistent().await?; - assert_eq!(read.value, 50); - let initial_random = read.random; - - // Snapshot + evict. The persistent `compute` task has a transient dependent - // (this run_once closure), so it may be blocked from full eviction. But we - // still exercise the evict path — some tasks (like create_state) may be - // data-only evicted. - let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); - println!("transient_reader: snapshot had_data={had_data}, evicted: {counts:?}"); - assert!(had_data, "snapshot should have persisted data"); - - // Mutate state — this invalidates the persistent task, which must propagate - // to the transient reader (this closure) even after eviction. - state.set(99); - - let read = output.read_strongly_consistent().await?; - assert_eq!(read.value, 99); - assert_ne!( - read.random, initial_random, - "task should have been re-executed after invalidation" - ); - - // Second eviction cycle - let (_, counts2) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); - println!("transient_reader (2nd): evicted: {counts2:?}"); - - state.set(0); - - let read = output.read_strongly_consistent().await?; - assert_eq!(read.value, 0); - - anyhow::Ok(()) - }) - .await; + let result = tt + .run_once(async move { + unmark_top_level_task_may_leak_eventually_consistent_state(); + + // Create persistent state + compute tasks + let state_op = create_state(50); + let state_vc = state_op.resolve().strongly_consistent().await?; + let state = state_op.read_strongly_consistent().await?; + + let output = compute(state_vc); + let read = output.read_strongly_consistent().await?; + assert_eq!(read.value, 50); + let initial_random = read.random; + + // Snapshot + evict. The persistent `compute` task has a transient dependent + // (this run_once closure), so it may be blocked from full eviction. But we + // still exercise the evict path — some tasks (like create_state) may be + // data-only evicted. + let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); + println!("transient_reader: snapshot had_data={had_data}, evicted: {counts:?}"); + assert!(had_data, "snapshot should have persisted data"); + + // Mutate state — this invalidates the persistent task, which must propagate + // to the transient reader (this closure) even after eviction. + state.set(99); + + let read = output.read_strongly_consistent().await?; + assert_eq!(read.value, 99); + assert_ne!( + read.random, initial_random, + "task should have been re-executed after invalidation" + ); + + // Second eviction cycle + let (_, counts2) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); + println!("transient_reader (2nd): evicted: {counts2:?}"); + + state.set(0); + + let read = output.read_strongly_consistent().await?; + assert_eq!(read.value, 0); + + anyhow::Ok(()) + }) + .await; tt.stop_and_wait().await; result.unwrap(); } @@ -525,61 +530,62 @@ async fn eviction_stress_concurrent() { } }); - let result = turbo_tasks::run_once(tt.clone(), async move { - unmark_top_level_task_may_leak_eventually_consistent_state(); - - let state_op = create_state(1); - let state_vc = state_op.resolve().strongly_consistent().await?; - let state = state_op.read_strongly_consistent().await?; - - // fan_out creates width * 2 intermediate tasks per call - let width = 20u32; - let output = fan_out(state_vc, width); - - // Helper: compute the expected fan_out result for a given state value. - // fan_out sums (state + i) * (i + 2) for i in 0..width. - let expected_for = |state_val: u32| -> u32 { - (0..width) - .map(|i| state_val.wrapping_add(i).wrapping_mul(i.wrapping_add(2))) - .fold(0u32, |acc, x| acc.wrapping_add(x)) - }; - - // Initial read to populate all tasks in memory, then wait for the - // background eviction thread to snapshot + evict at least once so data - // is on disk and eligible for eviction on subsequent cycles. - let read = *output.read_strongly_consistent().await?; - assert_eq!(read, expected_for(1)); - // Give the background eviction thread time to run a snapshot+evict cycle. - tokio::time::sleep(std::time::Duration::from_millis(50)).await; - - // Run invalidation cycles while the background eviction thread is active. - // The sleep between eviction cycles gives worker threads time to start - // restoring, then eviction runs and races with in-flight restores. - for i in 1u32..=50 { - state.set(i); - let read = tokio::time::timeout( - std::time::Duration::from_secs(5), - output.read_strongly_consistent(), - ) - .await - .unwrap_or_else(|_| { - panic!( - "cycle {i}: timed out waiting for read — likely a restore/eviction race \ - corrupted the task graph" + let result = tt + .run_once(async move { + unmark_top_level_task_may_leak_eventually_consistent_state(); + + let state_op = create_state(1); + let state_vc = state_op.resolve().strongly_consistent().await?; + let state = state_op.read_strongly_consistent().await?; + + // fan_out creates width * 2 intermediate tasks per call + let width = 20u32; + let output = fan_out(state_vc, width); + + // Helper: compute the expected fan_out result for a given state value. + // fan_out sums (state + i) * (i + 2) for i in 0..width. + let expected_for = |state_val: u32| -> u32 { + (0..width) + .map(|i| state_val.wrapping_add(i).wrapping_mul(i.wrapping_add(2))) + .fold(0u32, |acc, x| acc.wrapping_add(x)) + }; + + // Initial read to populate all tasks in memory, then wait for the + // background eviction thread to snapshot + evict at least once so data + // is on disk and eligible for eviction on subsequent cycles. + let read = *output.read_strongly_consistent().await?; + assert_eq!(read, expected_for(1)); + // Give the background eviction thread time to run a snapshot+evict cycle. + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + + // Run invalidation cycles while the background eviction thread is active. + // The sleep between eviction cycles gives worker threads time to start + // restoring, then eviction runs and races with in-flight restores. + for i in 1u32..=50 { + state.set(i); + let read = tokio::time::timeout( + std::time::Duration::from_secs(5), + output.read_strongly_consistent(), ) - })?; - let read = *read; - assert_eq!( - read, - expected_for(i), - "cycle {i}: expected {}, got {read}", - expected_for(i) - ); - } - - anyhow::Ok(()) - }) - .await; + .await + .unwrap_or_else(|_| { + panic!( + "cycle {i}: timed out waiting for read — likely a restore/eviction race \ + corrupted the task graph" + ) + })?; + let read = *read; + assert_eq!( + read, + expected_for(i), + "cycle {i}: expected {}, got {read}", + expected_for(i) + ); + } + + anyhow::Ok(()) + }) + .await; stop.store(true, Ordering::Relaxed); eviction_handle.await.unwrap(); @@ -667,60 +673,61 @@ async fn eviction_persistable_never_preserves_live_cell() { let (tt, _persistence_dir) = create_tt("eviction_persistable_never_preserves_live_cell"); let tt2 = tt.clone(); - let result = turbo_tasks::run_once(tt.clone(), async move { - unmark_top_level_task_may_leak_eventually_consistent_state(); + let result = tt + .run_once(async move { + unmark_top_level_task_may_leak_eventually_consistent_state(); + + let state_op = create_state(0); + let state_vc = state_op.resolve().strongly_consistent().await?; + let state = state_op.read_strongly_consistent().await?; + + // First read goes through `read_session_alive_id` so the writer + // (`create_session_alive`) has a persistent parent and is eligible + // for the eviction sweep without being held alive by run_once. + let pre = read_session_alive_id(state_vc) + .read_strongly_consistent() + .await?; + assert!(pre.alive, "freshly constructed cell should be alive"); + let live_ptr = pre.ptr; + drop(pre); + + // Snapshot + evict. `create_session_alive`'s `cell_data` should retain + // the SessionAlive cell as residue (Evictability::Never), while + // clearing `data_restored` and persisted data flag bits. + let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); + println!("persistable_never: snapshot had_data={had_data}, evicted: {counts:?}"); + assert!(had_data, "snapshot should have persisted data"); + + // Invalidate the reader so the next read re-runs `read_session_alive_id`. + // That re-execution reads `create_session_alive`'s cell, which goes + // through `task(.., Data)` and triggers `restore_data_from` — the buggy + // path here runs `extend(incoming)` over `cell_data` and replaces the + // live Arc with a freshly decoded one whose `alive` is default. + state.set(1); + + let post = read_session_alive_id(state_vc) + .read_strongly_consistent() + .await?; + println!( + "post-restore: alive={}, ptr_match={}", + post.alive, + post.ptr == live_ptr + ); - let state_op = create_state(0); - let state_vc = state_op.resolve().strongly_consistent().await?; - let state = state_op.read_strongly_consistent().await?; + assert_eq!( + post.ptr, live_ptr, + "post-restore cell must still hold the live `alive` Arc; a different pointer \ + means restore_data_from overwrote the residue with a freshly decoded copy" + ); + assert!( + post.alive, + "post-restore cell must still report alive=true; alive=false means the live cell \ + value was replaced by a decoded copy with default fields" + ); - // First read goes through `read_session_alive_id` so the writer - // (`create_session_alive`) has a persistent parent and is eligible - // for the eviction sweep without being held alive by run_once. - let pre = read_session_alive_id(state_vc) - .read_strongly_consistent() - .await?; - assert!(pre.alive, "freshly constructed cell should be alive"); - let live_ptr = pre.ptr; - drop(pre); - - // Snapshot + evict. `create_session_alive`'s `cell_data` should retain - // the SessionAlive cell as residue (Evictability::Never), while - // clearing `data_restored` and persisted data flag bits. - let (had_data, counts) = tt2.backend().snapshot_and_evict_for_testing(&*tt2); - println!("persistable_never: snapshot had_data={had_data}, evicted: {counts:?}"); - assert!(had_data, "snapshot should have persisted data"); - - // Invalidate the reader so the next read re-runs `read_session_alive_id`. - // That re-execution reads `create_session_alive`'s cell, which goes - // through `task(.., Data)` and triggers `restore_data_from` — the buggy - // path here runs `extend(incoming)` over `cell_data` and replaces the - // live Arc with a freshly decoded one whose `alive` is default. - state.set(1); - - let post = read_session_alive_id(state_vc) - .read_strongly_consistent() - .await?; - println!( - "post-restore: alive={}, ptr_match={}", - post.alive, - post.ptr == live_ptr - ); - - assert_eq!( - post.ptr, live_ptr, - "post-restore cell must still hold the live `alive` Arc; a different pointer means \ - restore_data_from overwrote the residue with a freshly decoded copy" - ); - assert!( - post.alive, - "post-restore cell must still report alive=true; alive=false means the live cell \ - value was replaced by a decoded copy with default fields" - ); - - anyhow::Ok(()) - }) - .await; + anyhow::Ok(()) + }) + .await; tt.stop_and_wait().await; result.unwrap(); } diff --git a/turbopack/crates/turbo-tasks-backend/tests/read_ref_cell.rs b/turbopack/crates/turbo-tasks-backend/tests/read_ref_cell.rs index 83c816a23d20..d200823798ba 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/read_ref_cell.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/read_ref_cell.rs @@ -69,7 +69,7 @@ impl Counter { lock.0 += 1; let invalidators = take(&mut lock.1); for i in invalidators { - i.invalidate(&**tt); + i.invalidate(tt); } }); } diff --git a/turbopack/crates/turbo-tasks-backend/tests/scope_stress.rs b/turbopack/crates/turbo-tasks-backend/tests/scope_stress.rs index f44073088052..e5d94f8f0b58 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/scope_stress.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/scope_stress.rs @@ -2,7 +2,7 @@ #![allow(clippy::needless_return)] // tokio macro-generated code doesn't respect this use anyhow::Result; -use turbo_tasks::{Completion, TryJoinIterExt, Vc, run_once}; +use turbo_tasks::{Completion, TryJoinIterExt, Vc}; use turbo_tasks_testing::{Registration, register, run_with_tt}; static REGISTRATION: Registration = register!(); @@ -19,7 +19,7 @@ async fn rectangle_stress() -> Result<()> { .map(|(a, b)| { let tt = tt.clone(); async move { - run_once(tt, async move { + tt.run_once(async move { rectangle(a, b).strongly_consistent().await?; Ok(Vc::<()>::default()) }) diff --git a/turbopack/crates/turbo-tasks-backend/tests/trait_ref_cell.rs b/turbopack/crates/turbo-tasks-backend/tests/trait_ref_cell.rs index f0d0710d2137..1b520148d838 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/trait_ref_cell.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/trait_ref_cell.rs @@ -78,7 +78,7 @@ impl Counter { lock.0 += 1; let invalidators = take(&mut lock.1); for i in invalidators { - i.invalidate(&**tt); + i.invalidate(tt); } }); } diff --git a/turbopack/crates/turbo-tasks-bytes/Cargo.toml b/turbopack/crates/turbo-tasks-bytes/Cargo.toml index 2c92b6c8fcc5..e45052ad5a3d 100644 --- a/turbopack/crates/turbo-tasks-bytes/Cargo.toml +++ b/turbopack/crates/turbo-tasks-bytes/Cargo.toml @@ -18,3 +18,10 @@ bytes = { workspace = true } futures = { workspace = true } serde = { workspace = true } turbo-tasks = { workspace = true } + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols in `libturbo_tasks.rlib` resolve in this crate's test binary. +# See the matching `extern crate` in `src/lib.rs` and the explanation in +# `turbo-esregex/Cargo.toml`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbo-tasks-bytes/src/lib.rs b/turbopack/crates/turbo-tasks-bytes/src/lib.rs index 43b55018100a..1ef8b691df31 100644 --- a/turbopack/crates/turbo-tasks-bytes/src/lib.rs +++ b/turbopack/crates/turbo-tasks-bytes/src/lib.rs @@ -1,6 +1,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub mod bytes; pub mod stream; diff --git a/turbopack/crates/turbo-tasks-env/Cargo.toml b/turbopack/crates/turbo-tasks-env/Cargo.toml index 9d1ea1d677bf..15b383c91e76 100644 --- a/turbopack/crates/turbo-tasks-env/Cargo.toml +++ b/turbopack/crates/turbo-tasks-env/Cargo.toml @@ -19,3 +19,9 @@ turbo-rcstr = { workspace = true } turbo-tasks = { workspace = true } turbo-tasks-fs = { workspace = true } +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching `extern +# crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } + diff --git a/turbopack/crates/turbo-tasks-env/src/lib.rs b/turbopack/crates/turbo-tasks-env/src/lib.rs index 54e941ef7c9c..28c0ef3e37bd 100644 --- a/turbopack/crates/turbo-tasks-env/src/lib.rs +++ b/turbopack/crates/turbo-tasks-env/src/lib.rs @@ -1,6 +1,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + mod command_line; mod custom; mod dotenv; diff --git a/turbopack/crates/turbo-tasks-fetch/src/client.rs b/turbopack/crates/turbo-tasks-fetch/src/client.rs index f9c156a0c1a1..06f61107682e 100644 --- a/turbopack/crates/turbo-tasks-fetch/src/client.rs +++ b/turbopack/crates/turbo-tasks-fetch/src/client.rs @@ -264,7 +264,8 @@ impl FetchClientConfig { // // Skip when dependency tracking is disabled (e.g. one-shot `next build`) since // invalidation panics without dependency tracking and the timer would be wasted work. - if turbo_tasks::turbo_tasks().is_tracking_dependencies() + let tt = turbo_tasks::turbo_tasks(); + if tt.is_tracking_dependencies() && let (Some(deadline_secs), Some(invalidator)) = (deadline_secs, invalidator) { // transform absolute deadline back to a relative duration for the sleep call @@ -285,8 +286,7 @@ impl FetchClientConfig { // FetchClientConfig to track outstanding timers and cancel them. turbo_tasks::spawn(async move { tokio::time::sleep(remaining).await; - invalidator - .invalidate_with_reason(&*turbo_tasks::turbo_tasks(), HttpTimeout {}); + invalidator.invalidate_with_reason(&tt, HttpTimeout {}); }); } } diff --git a/turbopack/crates/turbo-tasks-fetch/src/lib.rs b/turbopack/crates/turbo-tasks-fetch/src/lib.rs index 8b037cf9c00e..988a3f25d010 100644 --- a/turbopack/crates/turbo-tasks-fetch/src/lib.rs +++ b/turbopack/crates/turbo-tasks-fetch/src/lib.rs @@ -2,6 +2,12 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary. The backend is a regular dep, but rustc +// only adds rlibs to the link command for crates Rust code references. +#[cfg(test)] +extern crate turbo_tasks_backend; + mod client; mod error; mod response; diff --git a/turbopack/crates/turbo-tasks-fetch/tests/fetch.rs b/turbopack/crates/turbo-tasks-fetch/tests/fetch.rs index 409454eb03db..020cba8c29df 100644 --- a/turbopack/crates/turbo-tasks-fetch/tests/fetch.rs +++ b/turbopack/crates/turbo-tasks-fetch/tests/fetch.rs @@ -335,15 +335,16 @@ async fn ttl_invalidates_within_session() { let TestInstance { tt, .. } = REGISTRATION.create_turbo_tasks("ttl_invalidates_within_session", true); - let body = turbo_tasks::run_once(tt.clone(), { - let url = url.clone(); - async move { - let body = fetch_body(url).read_strongly_consistent().await?; - Ok((*body).clone()) - } - }) - .await - .unwrap(); + let body = tt + .run_once({ + let url = url.clone(); + async move { + let body = fetch_body(url).read_strongly_consistent().await?; + Ok((*body).clone()) + } + }) + .await + .unwrap(); assert_eq!(&*body, "v1"); // Change the server response @@ -360,15 +361,16 @@ async fn ttl_invalidates_within_session() { // The timer should have invalidated fetch_inner, so a new strongly consistent read // should re-fetch and return the updated body. - let body = turbo_tasks::run_once(tt.clone(), { - let url = url.clone(); - async move { - let body = fetch_body(url).read_strongly_consistent().await?; - Ok((*body).clone()) - } - }) - .await - .unwrap(); + let body = tt + .run_once({ + let url = url.clone(); + async move { + let body = fetch_body(url).read_strongly_consistent().await?; + Ok((*body).clone()) + } + }) + .await + .unwrap(); assert_eq!(&*body, "v2"); tt.stop_and_wait().await; @@ -397,15 +399,16 @@ async fn ttl_invalidates_on_session_restore() { // Session 1: fetch and cache let TestInstance { tt, .. } = REGISTRATION.create_turbo_tasks("ttl_invalidates_on_session_restore", true); - let body = turbo_tasks::run_once(tt.clone(), { - let url = url.clone(); - async move { - let body = fetch_body(url).read_strongly_consistent().await?; - Ok((*body).clone()) - } - }) - .await - .unwrap(); + let body = tt + .run_once({ + let url = url.clone(); + async move { + let body = fetch_body(url).read_strongly_consistent().await?; + Ok((*body).clone()) + } + }) + .await + .unwrap(); assert_eq!(&*body, "v1"); tt.stop_and_wait().await; @@ -429,7 +432,7 @@ async fn ttl_invalidates_on_session_restore() { // timer-triggered re-execution to settle. let TestInstance { tt, .. } = REGISTRATION.create_turbo_tasks("ttl_invalidates_on_session_restore", false); - turbo_tasks::run_once(tt.clone(), { + tt.run_once({ let url = url.clone(); async move { // First read returns the stale cached value, but triggers the timer @@ -443,15 +446,16 @@ async fn ttl_invalidates_on_session_restore() { // Wait for the timer to fire and re-execution to settle tokio::time::sleep(std::time::Duration::from_millis(500)).await; - let body = turbo_tasks::run_once(tt.clone(), { - let url = url.clone(); - async move { - let body = fetch_body(url).read_strongly_consistent().await?; - Ok((*body).clone()) - } - }) - .await - .unwrap(); + let body = tt + .run_once({ + let url = url.clone(); + async move { + let body = fetch_body(url).read_strongly_consistent().await?; + Ok((*body).clone()) + } + }) + .await + .unwrap(); assert_eq!(&*body, "v2"); tt.stop_and_wait().await; } @@ -487,15 +491,16 @@ async fn errors_retried_on_session_restore() { let TestInstance { tt, .. } = REGISTRATION.create_turbo_tasks("errors_retried_on_session_restore", true); - let is_err = turbo_tasks::run_once(tt.clone(), { - let url = url.clone(); - async move { - let is_err = *fetch_is_err(url).read_strongly_consistent().await?; - Ok(is_err) - } - }) - .await - .unwrap(); + let is_err = tt + .run_once({ + let url = url.clone(); + async move { + let is_err = *fetch_is_err(url).read_strongly_consistent().await?; + Ok(is_err) + } + }) + .await + .unwrap(); assert!(is_err, "first fetch should be an error"); tt.stop_and_wait().await; @@ -509,15 +514,16 @@ async fn errors_retried_on_session_restore() { let TestInstance { tt, .. } = REGISTRATION.create_turbo_tasks("errors_retried_on_session_restore", false); - let is_err = turbo_tasks::run_once(tt.clone(), { - let url = url.clone(); - async move { - let is_err = *fetch_is_err(url).read_strongly_consistent().await?; - Ok(is_err) - } - }) - .await - .unwrap(); + let is_err = tt + .run_once({ + let url = url.clone(); + async move { + let is_err = *fetch_is_err(url).read_strongly_consistent().await?; + Ok(is_err) + } + }) + .await + .unwrap(); assert!(!is_err, "second fetch should succeed after session restore"); tt.stop_and_wait().await; } diff --git a/turbopack/crates/turbo-tasks-fs/benches/mod.rs b/turbopack/crates/turbo-tasks-fs/benches/mod.rs index 1ab8a9293bbb..0b8db7ab3543 100644 --- a/turbopack/crates/turbo-tasks-fs/benches/mod.rs +++ b/turbopack/crates/turbo-tasks-fs/benches/mod.rs @@ -1,3 +1,7 @@ +// Force linking the static-dispatch providers — see the matching dep +// declaration on `turbo-tasks-backend` in `Cargo.toml`. +extern crate turbo_tasks_backend; + use std::{ fs, sync::{Arc, mpsc::channel}, diff --git a/turbopack/crates/turbo-tasks-fs/src/lib.rs b/turbopack/crates/turbo-tasks-fs/src/lib.rs index fd6cad52cc47..b38ad3af8a83 100644 --- a/turbopack/crates/turbo-tasks-fs/src/lib.rs +++ b/turbopack/crates/turbo-tasks-fs/src/lib.rs @@ -41,7 +41,7 @@ use std::{ io::{self, BufRead, BufReader, ErrorKind, Read, Write as _}, mem::take, path::{MAIN_SEPARATOR, Path, PathBuf}, - sync::{Arc, LazyLock, Weak}, + sync::{Arc, LazyLock}, time::Duration, }; @@ -63,8 +63,8 @@ use tracing::Instrument; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ Completion, Effect, EffectStateStorage, InvalidationReason, NonLocalValue, ReadRef, ResolvedVc, - TaskInput, TurboTasksApi, ValueToString, ValueToStringRef, Vc, debug::ValueDebugFormat, - emit_effect, parallel, trace::TraceRawVcs, turbo_tasks_weak, turbobail, turbofmt, + TaskInput, ValueToString, ValueToStringRef, Vc, debug::ValueDebugFormat, emit_effect, parallel, + trace::TraceRawVcs, turbo_tasks_weak, turbobail, turbofmt, }; use turbo_tasks_hash::{ DeterministicHash, DeterministicHasher, HashAlgorithm, deterministic_hash, hash_xxh3_hash64, @@ -275,7 +275,7 @@ struct DiskFileSystemInner { /// watcher. #[turbo_tasks(debug_ignore, trace_ignore)] #[bincode(skip, default = "turbo_tasks_weak")] - turbo_tasks: Weak, + turbo_tasks: turbo_tasks::TurboTasksWeakHandle, /// Used by invalidators when called from a non-tokio thread, specifically in the fs watcher. #[turbo_tasks(debug_ignore, trace_ignore)] #[bincode(skip, default = "Handle::current")] @@ -336,7 +336,7 @@ impl DiskFileSystemInner { path: full_path.to_string_lossy().into_owned(), }; for invalidator in invalidators { - invalidator.invalidate_with_reason(&*turbo_tasks, reason.clone()); + invalidator.invalidate_with_reason(&turbo_tasks, reason.clone()); } } } @@ -375,7 +375,7 @@ impl DiskFileSystemInner { .flat_map(|(_, invalidators)| invalidators.into_iter()) .collect::>(); parallel::for_each_owned(invalidators, |invalidator| { - invalidator.invalidate(&*turbo_tasks) + invalidator.invalidate(&turbo_tasks) }); } @@ -405,7 +405,7 @@ impl DiskFileSystemInner { }) .collect::>(); parallel::for_each_owned(invalidators, |(reason, invalidator)| { - invalidator.invalidate_with_reason(&*turbo_tasks, reason) + invalidator.invalidate_with_reason(&turbo_tasks, reason) }); } @@ -474,7 +474,7 @@ impl DiskFileSystemInner { drop(dir_invalidator_map); parallel::for_each_owned(invalidators, |(reason, invalidator)| { - invalidator.invalidate_with_reason(&*turbo_tasks, reason) + invalidator.invalidate_with_reason(&turbo_tasks, reason) }); } diff --git a/turbopack/crates/turbo-tasks-fs/src/watcher.rs b/turbopack/crates/turbo-tasks-fs/src/watcher.rs index 173fea7d5a39..4337fab42e6e 100644 --- a/turbopack/crates/turbo-tasks-fs/src/watcher.rs +++ b/turbopack/crates/turbo-tasks-fs/src/watcher.rs @@ -22,8 +22,8 @@ use tokio::sync::{RwLock, RwLockWriteGuard}; use tracing::instrument; use turbo_rcstr::RcStr; use turbo_tasks::{ - FxIndexSet, InvalidationReason, InvalidationReasonKind, Invalidator, TurboTasksApi, parallel, - spawn_thread, util::StaticOrArc, + FxIndexSet, InvalidationReason, InvalidationReasonKind, Invalidator, parallel, spawn_thread, + util::StaticOrArc, }; use crate::{ @@ -424,14 +424,14 @@ impl DiskWatcher { }) .collect::>(); parallel::for_each_owned(invalidators, |(reason, invalidator)| { - invalidator.invalidate_with_reason(&*turbo_tasks, reason); + invalidator.invalidate_with_reason(&turbo_tasks, reason); }); } else { let invalidators = iter .flat_map(|(_, invalidators)| invalidators.into_iter()) .collect::>(); parallel::for_each_owned(invalidators, |invalidator| { - invalidator.invalidate(&*turbo_tasks); + invalidator.invalidate(&turbo_tasks); }); } } @@ -710,14 +710,14 @@ impl DiskWatcher { let mut invalidator_map = fs_inner.invalidator_map.lock().unwrap(); invalidate_path( &fs_inner, - &*turbo_tasks, + &turbo_tasks, report_invalidation_reason, &mut invalidator_map, batched_invalidate_path.drain(), ); invalidate_path_and_children_execute( &fs_inner, - &*turbo_tasks, + &turbo_tasks, report_invalidation_reason, &mut invalidator_map, batched_invalidate_path_and_children.drain(), @@ -727,14 +727,14 @@ impl DiskWatcher { let mut dir_invalidator_map = fs_inner.dir_invalidator_map.lock().unwrap(); invalidate_path( &fs_inner, - &*turbo_tasks, + &turbo_tasks, report_invalidation_reason, &mut dir_invalidator_map, batched_invalidate_path_dir.drain(), ); invalidate_path_and_children_execute( &fs_inner, - &*turbo_tasks, + &turbo_tasks, report_invalidation_reason, &mut dir_invalidator_map, batched_invalidate_path_and_children_dir.drain(), @@ -772,7 +772,7 @@ impl DiskWatcher { )] fn invalidate( inner: &DiskFileSystemInner, - turbo_tasks: &dyn TurboTasksApi, + turbo_tasks: &turbo_tasks::TurboTasksHandle, report_invalidation_reason: bool, path: &Path, invalidator: Invalidator, @@ -788,7 +788,7 @@ fn invalidate( fn invalidate_path( inner: &DiskFileSystemInner, - turbo_tasks: &dyn TurboTasksApi, + turbo_tasks: &turbo_tasks::TurboTasksHandle, report_invalidation_reason: bool, invalidator_map: &mut LockedInvalidatorMap, paths: impl Iterator, @@ -804,7 +804,7 @@ fn invalidate_path( fn invalidate_path_and_children_execute( inner: &DiskFileSystemInner, - turbo_tasks: &dyn TurboTasksApi, + turbo_tasks: &turbo_tasks::TurboTasksHandle, report_invalidation_reason: bool, invalidator_map: &mut LockedInvalidatorMap, paths: impl Iterator, diff --git a/turbopack/crates/turbo-tasks-testing/Cargo.toml b/turbopack/crates/turbo-tasks-testing/Cargo.toml index 7571896f4576..9aa035b03a8f 100644 --- a/turbopack/crates/turbo-tasks-testing/Cargo.toml +++ b/turbopack/crates/turbo-tasks-testing/Cargo.toml @@ -20,4 +20,7 @@ rustc-hash = { workspace = true } smallvec = { workspace = true } tokio = { workspace = true } turbo-tasks = { workspace = true, features = ["non_operation_vc_strongly_consistent"] } +# The `test_instance` factory goes through `make_handle`, so binaries +# that depend on this crate transitively pull in the +# `turbo-tasks-backend` rlib for the `__tt_static_*` providers. turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbo-tasks-testing/src/lib.rs b/turbopack/crates/turbo-tasks-testing/src/lib.rs index 64e9d7033b6a..6cda42b297bd 100644 --- a/turbopack/crates/turbo-tasks-testing/src/lib.rs +++ b/turbopack/crates/turbo-tasks-testing/src/lib.rs @@ -1,6 +1,5 @@ //! Testing utilities and macros for turbo-tasks and applications based on it. -pub mod retry; mod run; pub use crate::run::{ diff --git a/turbopack/crates/turbo-tasks-testing/src/run.rs b/turbopack/crates/turbo-tasks-testing/src/run.rs index b51facbcafb5..184688ce4ece 100644 --- a/turbopack/crates/turbo-tasks-testing/src/run.rs +++ b/turbopack/crates/turbo-tasks-testing/src/run.rs @@ -1,24 +1,24 @@ use std::{env, fmt::Debug, future::Future, sync::Arc}; use anyhow::Result; -use turbo_tasks::{TurboTasks, TurboTasksApi, trace::TraceRawVcs}; -use turbo_tasks_backend::{BackingStorage, TurboTasksBackend}; +use turbo_tasks::{TurboTasks, trace::TraceRawVcs}; +use turbo_tasks_backend::{TurboBackingStorage, TurboTasksBackend}; -/// A freshly created test instance: the `TurboTasks` handle (type-erased to -/// `Arc`) and a closure that, when called, takes a -/// snapshot and evicts all evictable tasks on that instance. -/// -/// The eviction closure captures the concrete backend type internally so -/// harness code holding an erased `TurboTasksApi` can still reach the -/// `snapshot_and_evict` API. +/// Concrete `TurboTasks` arc used by every test harness in the workspace. +/// All `test_config.trs` files in this workspace produce this exact type, +/// so the test surface can hold it directly instead of going through the +/// type-erased `TurboTasksHandle`. +pub type TestTurboTasks = Arc>>; + +/// A freshly created test instance: the concrete `TurboTasks` arc and a +/// closure that, when called, takes a snapshot and evicts all evictable +/// tasks on that instance. pub struct TestInstance { - pub tt: Arc, + pub tt: TestTurboTasks, pub snapshot_and_evict: Box, } -/// Type-erased factory returned by the `register!` macro. Stays non-generic so -/// call sites can write `static REGISTRATION: Registration = register!();` -/// without naming the backing storage type. +/// Factory returned by the `register!` macro. pub struct Registration { create_turbo_tasks: fn(&str, bool) -> TestInstance, } @@ -34,14 +34,10 @@ impl Registration { } } -/// Wrap a concrete `Arc>>` into a -/// [`TestInstance`]. Called from the `register!` macro — the `.trs` closure -/// returns a concrete backend-parameterized `TurboTasks`, and this function -/// erases the type while retaining eviction access via a capturing closure. -pub fn test_instance(tt: Arc>>) -> TestInstance -where - B: BackingStorage + 'static, -{ +/// Wrap a concrete `TestTurboTasks` into a [`TestInstance`]. Called from +/// the `register!` macro — the `.trs` closure returns the concrete arc, +/// and this function attaches an eviction closure for the test harness. +pub fn test_instance(tt: TestTurboTasks) -> TestInstance { let tt_for_evict = tt.clone(); let snapshot_and_evict = Box::new(move || { let _ = tt_for_evict @@ -49,7 +45,7 @@ where .snapshot_and_evict_for_testing(&*tt_for_evict); }); TestInstance { - tt: tt as Arc, + tt, snapshot_and_evict, } } @@ -77,7 +73,9 @@ where { let name = closure_to_name(&fut); let instance = registration.create_turbo_tasks(&name, true); - turbo_tasks::run_once(instance.tt, async move { Ok(fut.await) }) + instance + .tt + .run_once(async move { Ok(fut.await) }) .await .unwrap() } @@ -91,9 +89,7 @@ where { let name = closure_to_name(&fut); let instance = registration.create_turbo_tasks(&name, true); - turbo_tasks::run(instance.tt, async move { Ok(fut.await) }) - .await - .unwrap() + instance.tt.run(async move { Ok(fut.await) }).await.unwrap() } fn closure_to_name(value: &T) -> String { @@ -109,7 +105,11 @@ where F: Future> + Send + 'static, T: Debug + PartialEq + Eq + TraceRawVcs + Send + 'static, { - run_with_tt(registration, move |tt| turbo_tasks::run_once(tt, fut())).await + run_with_tt(registration, move |tt| { + let f = fut(); + async move { tt.run_once(f).await } + }) + .await } pub async fn run( @@ -120,12 +120,16 @@ where F: Future> + Send + 'static, T: Debug + PartialEq + Eq + TraceRawVcs + Send + 'static, { - run_with_tt(registration, move |tt| turbo_tasks::run(tt, fut())).await + run_with_tt(registration, move |tt| { + let f = fut(); + async move { Ok(tt.run(f).await?) } + }) + .await } pub async fn run_with_tt( registration: &Registration, - mut fut: impl FnMut(Arc) -> F + Send + 'static, + mut fut: impl FnMut(TestTurboTasks) -> F + Send + 'static, ) -> Result<()> where F: Future> + Send + 'static, diff --git a/turbopack/crates/turbo-tasks/Cargo.toml b/turbopack/crates/turbo-tasks/Cargo.toml index 6f1733067135..7beddaf865be 100644 --- a/turbopack/crates/turbo-tasks/Cargo.toml +++ b/turbopack/crates/turbo-tasks/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" description = "TBD" license = "MIT" edition = "2024" +# Disable auto-discovery of bench targets so `benches/scope.rs` is +# treated as a module of `benches/mod.rs` rather than a separate bench. +autobenches = false [lib] bench = false @@ -61,6 +64,10 @@ unsize = { workspace = true } [dev-dependencies] criterion = { workspace = true, features = ["async_tokio"] } +# Circular dev-dep — Cargo allows it. Pulls in the `__tt_static_*` +# providers so this crate's test/bench binaries can link. See the +# matching `extern crate` in `src/lib.rs` and `benches/mod.rs`. +turbo-tasks-backend = { workspace = true } [[bench]] name = "mod" diff --git a/turbopack/crates/turbo-tasks/benches/mod.rs b/turbopack/crates/turbo-tasks/benches/mod.rs index 60e504c700d9..2da476d0c692 100644 --- a/turbopack/crates/turbo-tasks/benches/mod.rs +++ b/turbopack/crates/turbo-tasks/benches/mod.rs @@ -1,5 +1,9 @@ #![feature(arbitrary_self_types)] +// Force linking the static-dispatch providers — see the matching +// `extern crate` and dev-dep declaration on `turbo-tasks-backend`. +extern crate turbo_tasks_backend; + use criterion::{Criterion, criterion_group, criterion_main}; pub(crate) mod scope; diff --git a/turbopack/crates/turbo-tasks/src/backend.rs b/turbopack/crates/turbo-tasks/src/backend.rs index e69369a7c297..631c552d3e96 100644 --- a/turbopack/crates/turbo-tasks/src/backend.rs +++ b/turbopack/crates/turbo-tasks/src/backend.rs @@ -29,8 +29,8 @@ use turbo_tasks_hash::DeterministicHasher; use crate::{ RawVc, ReadCellOptions, ReadOutputOptions, ReadRef, SharedReference, TaskId, TaskIdSet, - TaskPriority, TraitRef, TraitTypeId, TurboTasksCallApi, TurboTasksPanic, ValueTypeId, - ValueTypePersistence, VcValueTrait, VcValueType, + TaskPriority, TraitRef, TraitTypeId, TurboTasksPanic, ValueTypeId, ValueTypePersistence, + VcValueTrait, VcValueType, dyn_task_inputs::{DynTaskInputs, StackDynTaskInputs}, event::EventListener, macro_helpers::NativeFunction, @@ -371,7 +371,7 @@ pub struct TurboTasksError { /// [`TurboTasksCallApi::get_task_name`]) rather than eagerly at error creation time. #[derive(Clone)] pub struct TurboTaskContextError { - pub turbo_tasks: Arc, + pub turbo_tasks: crate::TurboTasksHandle, pub task_id: TaskId, pub source: Option, } @@ -434,11 +434,7 @@ pub enum TurboTasksExecutionError { impl TurboTasksExecutionError { /// Wraps this error in a [`TaskContext`](TurboTasksExecutionError::TaskContext) layer /// identifying the normal task that encountered the error. - pub fn with_task_context( - self, - task_id: TaskId, - turbo_tasks: Arc, - ) -> Self { + pub fn with_task_context(self, task_id: TaskId, turbo_tasks: crate::TurboTasksHandle) -> Self { TurboTasksExecutionError::TaskContext(Arc::new(TurboTaskContextError { task_id, turbo_tasks, diff --git a/turbopack/crates/turbo-tasks/src/handle.rs b/turbopack/crates/turbo-tasks/src/handle.rs new file mode 100644 index 000000000000..ad0137e5fd2d --- /dev/null +++ b/turbopack/crates/turbo-tasks/src/handle.rs @@ -0,0 +1,350 @@ +//! Devirtualized dispatch for the turbo-tasks task-local. +//! +//! The task-local that holds the current `TurboTasksApi` implementation +//! has historically been an `Arc`. The `dyn` is +//! necessary because the prod implementor (`TurboTasks`) is generic +//! over a backend type that the `task_local!` macro cannot name — but +//! the cost is an indirect vtable call on every dispatched method, and +//! rustc currently does not emit the LLVM metadata that +//! `WholeProgramDevirt` needs to inline through trait objects +//! ([rust#68262], [rust#45774]). +//! +//! [rust#68262]: https://github.com/rust-lang/rust/issues/68262 +//! [rust#45774]: https://github.com/rust-lang/rust/issues/45774 +//! +//! This module replaces that `dyn` with `extern "Rust"` direct calls. +//! Under `lto = "thin"` + `codegen-units = 1` (this workspace's release +//! profile), the `extern "Rust"` call inlines across the +//! `turbo-tasks` → `turbo-tasks-backend` boundary, collapsing the +//! dispatch site to a direct call into the underlying backend method. +//! +//! ```text +//! call site turbo-tasks-backend +//! ┌──────────────────────────┐ ┌─────────────────────────────────────┐ +//! │ tt.invalidate(task) │ ───────────► │ #[no_mangle] pub extern "Rust" fn │ +//! │ __tt_static_invalidate(ptr, task) │ __tt_static_invalidate(ptr, task) { │ +//! │ │ │ let tt: &TurboTasks<…> = …; │ +//! │ │ │ tt.invalidate(task) │ +//! └──────────────────────────┘ │ } │ +//! └─────────────────────────────────────┘ +//! ``` +//! +//! ## Linkage contract +//! +//! `turbo-tasks` declares the `__tt_static_*` symbols as `extern "Rust"` +//! forward declarations unconditionally. `turbo-tasks-backend` defines +//! them as `#[no_mangle] pub extern "Rust" fn`. Every binary that +//! links `libturbo_tasks.rlib` MUST also link +//! `libturbo_tasks_backend.rlib` so the linker can resolve the symbols +//! — there's no feature flag to gate this off. +//! +//! For library crates that depend on `turbo-tasks` for the proc-macro +//! types (`turbo_tasks::value`, `turbo_tasks::function`) but never +//! construct a `TurboTasks`, that's still required because their +//! test binaries link the rlib. The convention is a dev-dep on +//! `turbo-tasks-backend` plus `#[cfg(test)] extern crate +//! turbo_tasks_backend;` in `src/lib.rs` — see the existing examples in +//! `turbo-tasks-bytes`, `turbo-esregex`, etc. +//! +//! ## Follow-ups +//! +//! - `task_statistics` is only used by `turbo-tasks-backend/tests/task_statistics.rs`; if that test +//! calls it on a concrete `TurboTasks` instead of via the handle, it can move off the dispatch +//! surface (no extern, no method on `TurboTasksHandle`). + +use std::ptr::NonNull; + +/// Type-erased reference to a `TurboTasksApi` implementation. See the +/// [module docs](self) for the dispatch design. +/// +/// The pointer is the data pointer of an `Arc::into_raw(arc)` where +/// `arc: Arc>` for the concrete backend type the +/// `__tt_static_*` providers were generated for. Dispatch goes through +/// `extern "Rust"` symbols defined in `turbo-tasks-backend`. +pub struct TurboTasksHandle(NonNull<()>); + +impl std::fmt::Debug for TurboTasksHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("TurboTasksHandle").field(&self.0).finish() + } +} + +// Safety: the concrete `Arc>` behind the pointer is +// `Send + Sync`. We strip the type via `*const ()` but keep the +// underlying ref-counted ownership intact. +unsafe impl Send for TurboTasksHandle {} +unsafe impl Sync for TurboTasksHandle {} + +impl TurboTasksHandle { + /// Construct a handle from an `Arc::into_raw` pointer. + /// + /// # Safety + /// + /// `ptr` must come from `Arc::into_raw(arc)` where `arc` is an + /// `Arc>` for the concrete backend the + /// `__tt_static_*` providers (in `turbo-tasks-backend`) target. + /// Ownership of one strong refcount transfers into the handle. + #[inline] + pub unsafe fn from_static_raw(ptr: NonNull<()>) -> Self { + Self(ptr) + } +} + +// ===================================================================== +// `extern "Rust"` forward declarations. +// +// The bodies are defined `#[no_mangle]` in `turbo-tasks-backend` and +// resolved at link time. Thin LTO inlines them across the crate boundary. +// ===================================================================== + +/// For each dispatched method, generates both the `extern "Rust"` forward +/// declaration of `__tt_static_` and the matching inherent method on +/// `TurboTasksHandle` that calls it. +macro_rules! tt_decl { + ( + fn $name:ident( $($arg:ident : $ty:ty),* $(,)? ) $(-> $ret:ty)? + ) => { + unsafe extern "Rust" { + fn ${concat(__tt_static_, $name)}(ptr: *const () $(, $arg : $ty)*) $(-> $ret)?; + } + impl TurboTasksHandle { + #[inline] + pub fn $name(&self $(, $arg : $ty)*) $(-> $ret)? { + unsafe { ${concat(__tt_static_, $name)}(self.0.as_ptr() $(, $arg)*) } + } + } + }; +} + +// ---- dispatched methods ------------------------------------------------- +// +// Keep this list in sync with the matching provider implementations in +// `turbo-tasks-backend/src/handle_providers.rs`. The list is duplicated; +// a missing provider surfaces as a link error. + +// `TurboTasksCallApi` methods. +tt_decl!(fn dynamic_call( + native_fn: &'static crate::native_function::NativeFunction, + this: Option, + arg: &mut dyn crate::StackDynTaskInputs, + persistence: crate::TaskPersistence, +) -> crate::RawVc); + +tt_decl!(fn native_call( + native_fn: &'static crate::native_function::NativeFunction, + this: Option, + arg: &mut dyn crate::StackDynTaskInputs, + persistence: crate::TaskPersistence, +) -> crate::RawVc); + +tt_decl!(fn trait_call( + trait_method: &'static crate::TraitMethod, + this: crate::RawVc, + arg: &mut dyn crate::StackDynTaskInputs, + persistence: crate::TaskPersistence, +) -> crate::RawVc); + +tt_decl!(fn send_compilation_event( + event: ::std::sync::Arc, +)); + +tt_decl!(fn get_task_name(task: crate::TaskId) -> ::std::string::String); + +// `TurboTasksApi` methods (inherits TurboTasksCallApi above). +tt_decl!(fn invalidate(task: crate::TaskId)); + +tt_decl!(fn invalidate_with_reason( + task: crate::TaskId, + reason: crate::util::StaticOrArc, +)); + +tt_decl!(fn invalidate_serialization(task: crate::TaskId)); + +tt_decl!(fn try_read_task_output( + task: crate::TaskId, + options: crate::ReadOutputOptions, +) -> ::anyhow::Result<::core::result::Result>); + +tt_decl!(fn try_read_task_cell( + task: crate::TaskId, + index: crate::CellId, + options: crate::ReadCellOptions, +) -> ::anyhow::Result<::core::result::Result>); + +tt_decl!(fn try_read_local_output( + execution_id: crate::ExecutionId, + local_task_id: crate::LocalTaskId, +) -> ::anyhow::Result<::core::result::Result>); + +tt_decl!(fn read_task_collectibles( + task: crate::TaskId, + trait_id: crate::TraitTypeId, +) -> crate::backend::TaskCollectiblesMap); + +tt_decl!(fn emit_collectible( + trait_type: crate::TraitTypeId, + collectible: crate::RawVc, +)); + +tt_decl!(fn unemit_collectible( + trait_type: crate::TraitTypeId, + collectible: crate::RawVc, + count: u32, +)); + +tt_decl!(fn unemit_collectibles( + trait_type: crate::TraitTypeId, + collectibles: &crate::backend::TaskCollectiblesMap, +)); + +tt_decl!(fn try_read_own_task_cell( + current_task: crate::TaskId, + index: crate::CellId, +) -> ::anyhow::Result); + +tt_decl!(fn read_own_task_cell( + task: crate::TaskId, + index: crate::CellId, +) -> ::anyhow::Result); + +tt_decl!(fn update_own_task_cell( + task: crate::TaskId, + index: crate::CellId, + content: crate::backend::CellContent, + updated_key_hashes: ::core::option::Option<::smallvec::SmallVec<[u64; 2]>>, + content_hash: ::core::option::Option, + verification_mode: crate::backend::VerificationMode, +)); + +tt_decl!(fn mark_own_task_as_finished(task: crate::TaskId)); + +tt_decl!(fn connect_task(task: crate::TaskId)); + +tt_decl!(fn spawn_detached_for_testing( + f: ::std::pin::Pin<::std::boxed::Box + ::core::marker::Send + 'static>>, +)); + +tt_decl!(fn is_tracking_dependencies() -> bool); + +// `task_statistics` returns `&TaskStatisticsApi` borrowed from `&self`. +// The macro can't express the lifetime relationship through a `*const ()` +// receiver, so the provider returns `*const TaskStatisticsApi` and the +// handle wrapper re-binds the lifetime to `&self`. +unsafe extern "Rust" { + fn __tt_static_task_statistics( + ptr: *const (), + ) -> *const crate::task_statistics::TaskStatisticsApi; +} + +impl TurboTasksHandle { + #[inline] + pub fn task_statistics(&self) -> &crate::task_statistics::TaskStatisticsApi { + // SAFETY: the provider returns a pointer to a + // `TaskStatisticsApi` owned by the underlying `TurboTasks`, + // which this handle keeps alive via its Arc. The returned + // reference is bound to `&self`. + let p = unsafe { __tt_static_task_statistics(self.0.as_ptr()) }; + unsafe { &*p } + } +} + +// ===================================================================== +// Clone / Drop — Arc refcounting through extern symbols. +// ===================================================================== + +unsafe extern "Rust" { + fn __tt_static_clone_arc(ptr: *const ()); + fn __tt_static_drop_arc(ptr: *const ()); + + // Weak-handle support. Each provides: + // downgrade : *const Arc -> *const Weak (creates a fresh + // Weak; caller owns the returned weak). + // upgrade : *const Weak -> *const Arc (returns null if + // the Arc is gone; otherwise transfers one strong + // refcount). + // clone_weak: bumps the weak refcount. + // drop_weak : drops the weak refcount. + fn __tt_static_downgrade(arc_ptr: *const ()) -> *const (); + fn __tt_static_upgrade(weak_ptr: *const ()) -> *const (); + fn __tt_static_clone_weak(weak_ptr: *const ()); + fn __tt_static_drop_weak(weak_ptr: *const ()); +} + +impl Clone for TurboTasksHandle { + #[inline] + fn clone(&self) -> Self { + unsafe { __tt_static_clone_arc(self.0.as_ptr()) } + Self(self.0) + } +} + +impl Drop for TurboTasksHandle { + #[inline] + fn drop(&mut self) { + unsafe { __tt_static_drop_arc(self.0.as_ptr()) } + } +} + +impl TurboTasksHandle { + /// Downgrades to a weak handle, equivalent to `Arc::downgrade`. + #[inline] + pub fn downgrade(&self) -> TurboTasksWeakHandle { + let weak_ptr = unsafe { __tt_static_downgrade(self.0.as_ptr()) }; + // `Weak::into_raw` always produces a valid (non-null) pointer + // even when the strong count is zero. + TurboTasksWeakHandle(unsafe { NonNull::new_unchecked(weak_ptr as *mut ()) }) + } +} + +// ===================================================================== +// Weak-handle dispatch. +// +// Used by long-lived non-task contexts (e.g. the filesystem watcher in +// `turbo-tasks-fs`) that need to reach back into TurboTasks without +// keeping it alive. +// ===================================================================== + +/// Weak counterpart to [`TurboTasksHandle`]. Constructed via +/// [`TurboTasksHandle::downgrade`]; upgraded via +/// [`TurboTasksWeakHandle::upgrade`]. +pub struct TurboTasksWeakHandle(NonNull<()>); + +impl std::fmt::Debug for TurboTasksWeakHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("TurboTasksWeakHandle") + .field(&self.0) + .finish() + } +} + +// Safety: same reasoning as for `TurboTasksHandle`. +unsafe impl Send for TurboTasksWeakHandle {} +unsafe impl Sync for TurboTasksWeakHandle {} + +impl TurboTasksWeakHandle { + /// Tries to recover a strong handle. Returns `None` if the underlying + /// concrete handle has been dropped. + #[inline] + pub fn upgrade(&self) -> Option { + let strong = unsafe { __tt_static_upgrade(self.0.as_ptr()) }; + let strong = NonNull::new(strong as *mut ())?; + // SAFETY: provider returned a valid `Arc::into_raw` pointer for + // the concrete type, transferring one strong refcount. + Some(unsafe { TurboTasksHandle::from_static_raw(strong) }) + } +} + +impl Clone for TurboTasksWeakHandle { + #[inline] + fn clone(&self) -> Self { + unsafe { __tt_static_clone_weak(self.0.as_ptr()) } + Self(self.0) + } +} + +impl Drop for TurboTasksWeakHandle { + #[inline] + fn drop(&mut self) { + unsafe { __tt_static_drop_weak(self.0.as_ptr()) } + } +} diff --git a/turbopack/crates/turbo-tasks/src/invalidation.rs b/turbopack/crates/turbo-tasks/src/invalidation.rs index 8dc999006016..638eb17bbc79 100644 --- a/turbopack/crates/turbo-tasks/src/invalidation.rs +++ b/turbopack/crates/turbo-tasks/src/invalidation.rs @@ -7,7 +7,7 @@ use turbo_dyn_eq_hash::{ }; use crate::{ - FxIndexMap, FxIndexSet, NonLocalValue, OperationValue, TaskId, TurboTasksApi, + FxIndexMap, FxIndexSet, NonLocalValue, OperationValue, TaskId, manager::{current_task_if_available, mark_invalidator}, trace::TraceRawVcs, util::StaticOrArc, @@ -33,13 +33,13 @@ pub struct Invalidator { } impl Invalidator { - pub fn invalidate(self, turbo_tasks: &dyn TurboTasksApi) { + pub fn invalidate(self, turbo_tasks: &crate::TurboTasksHandle) { turbo_tasks.invalidate(self.task); } pub fn invalidate_with_reason( self, - turbo_tasks: &dyn TurboTasksApi, + turbo_tasks: &crate::TurboTasksHandle, reason: T, ) { turbo_tasks.invalidate_with_reason( diff --git a/turbopack/crates/turbo-tasks/src/lib.rs b/turbopack/crates/turbo-tasks/src/lib.rs index 6391b50c8597..10c4fba0a551 100644 --- a/turbopack/crates/turbo-tasks/src/lib.rs +++ b/turbopack/crates/turbo-tasks/src/lib.rs @@ -10,6 +10,14 @@ #![feature(async_fn_traits)] #![feature(impl_trait_in_assoc_type)] #![feature(const_type_name)] +#![feature(macro_metavar_expr_concat)] + +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test/bench binaries (the circular dev-dep is declared in +// `Cargo.toml`). rustc only adds rlibs to the link command for crates +// Rust code references — this `extern crate` is the reference. +#[cfg(test)] +extern crate turbo_tasks_backend; pub mod backend; mod capture_future; @@ -24,6 +32,7 @@ mod effect; mod error; pub mod event; pub mod graph; +mod handle; mod id; mod id_factory; mod invalidation; @@ -81,6 +90,7 @@ pub use crate::{ }, effect::{Effect, EffectError, EffectStateStorage, Effects, emit_effect, take_effects}, error::PrettyPrintError, + handle::{TurboTasksHandle, TurboTasksWeakHandle}, id::{ExecutionId, LocalTaskId, TRANSIENT_TASK_BIT, TaskId, TraitTypeId, ValueTypeId}, invalidation::{ InvalidationReason, InvalidationReasonKind, InvalidationReasonSet, Invalidator, @@ -91,9 +101,9 @@ pub use crate::{ CurrentCellRef, ReadCellTracking, ReadConsistency, ReadTracking, TaskPersistence, TaskPriority, TurboTasks, TurboTasksApi, TurboTasksCallApi, Unused, UpdateInfo, dynamic_call, emit, get_serialization_invalidator, mark_finished, mark_stateful, - mark_top_level_task, prevent_gc, run, run_once, run_once_with_reason, trait_call, - turbo_tasks, turbo_tasks_scope, turbo_tasks_weak, - unmark_top_level_task_may_leak_eventually_consistent_state, with_turbo_tasks, + mark_top_level_task, prevent_gc, trait_call, turbo_tasks, turbo_tasks_scope, + turbo_tasks_weak, unmark_top_level_task_may_leak_eventually_consistent_state, + with_turbo_tasks, }, mapped_read_ref::MappedReadRef, output::OutputContent, diff --git a/turbopack/crates/turbo-tasks/src/manager.rs b/turbopack/crates/turbo-tasks/src/manager.rs index c40a19b0d275..b2418b3d9879 100644 --- a/turbopack/crates/turbo-tasks/src/manager.rs +++ b/turbopack/crates/turbo-tasks/src/manager.rs @@ -28,8 +28,8 @@ use turbo_tasks_hash::{DeterministicHash, hash_xxh3_hash128}; use crate::{ Completion, InvalidationReason, InvalidationReasonSet, OutputContent, ReadCellOptions, - ReadOutputOptions, ResolvedVc, SharedReference, TaskId, TraitMethod, ValueTypeId, Vc, VcRead, - VcValueTrait, VcValueType, + ReadOutputOptions, ResolvedVc, SharedReference, TaskId, TraitMethod, TurboTasksHandle, + ValueTypeId, Vc, VcRead, VcValueTrait, VcValueType, backend::{ Backend, CellContent, CellHash, TaskCollectiblesMap, TaskExecutionSpec, TransientTaskType, TurboTasksExecutionError, TypedCellContent, VerificationMode, @@ -83,14 +83,6 @@ pub trait TurboTasksCallApi: Sync + Send { persistence: TaskPersistence, ) -> RawVc; - fn run( - &self, - future: Pin> + Send + 'static>>, - ) -> Pin> + Send>>; - fn run_once( - &self, - future: Pin> + Send + 'static>>, - ) -> Pin> + Send>>; fn run_once_with_reason( &self, reason: StaticOrArc, @@ -185,8 +177,6 @@ pub trait TurboTasksApi: TurboTasksCallApi + Sync + Send { fn task_statistics(&self) -> &TaskStatisticsApi; - fn stop_and_wait(&self) -> Pin + Send>>; - fn subscribe_to_compilation_events( &self, event_types: Option>, @@ -542,8 +532,11 @@ impl CurrentTaskState { // TODO implement our own thread pool and make these thread locals instead task_local! { - /// The current TurboTasks instance - static TURBO_TASKS: Arc; + /// The current TurboTasks instance. A [`TurboTasksHandle`] is a + /// tagged pointer that dispatches `TurboTasksApi` method calls + /// through `extern "Rust"` symbols provided by `turbo-tasks-handle`. + /// See [`crate::handle`] for the dispatch design. + static TURBO_TASKS: TurboTasksHandle; static CURRENT_TASK_STATE: Arc>; @@ -592,6 +585,22 @@ impl TurboTasks { self.this.upgrade().unwrap() } + /// Builds a [`TurboTasksHandle`] that points at this `TurboTasks`. + /// Consumes one strong refcount from the given `Arc`; the handle + /// will drop that refcount when itself dropped. + pub fn make_handle(self: Arc) -> crate::TurboTasksHandle { + let ptr = Arc::into_raw(self) as *mut (); + // Safety: `ptr` came from `Arc::into_raw` on a `TurboTasks`, + // which the `__tt_static_*` providers (in `turbo-tasks-backend`) + // know how to cast back to. + unsafe { crate::TurboTasksHandle::from_static_raw(std::ptr::NonNull::new_unchecked(ptr)) } + } + + /// Builds a [`TurboTasksHandle`] for this `TurboTasks` instance. + fn make_handle_from_ref(&self) -> crate::TurboTasksHandle { + self.pin().make_handle() + } + /// Creates a new root task pub fn spawn_root_task(&self, functor: F) -> TaskId where @@ -670,7 +679,7 @@ impl TurboTasks { let result = TURBO_TASKS .scope( - self.pin(), + self.make_handle_from_ref(), CURRENT_TASK_STATE.scope(current_task_state, async { let result = CaptureFuture::new(future).await; @@ -902,17 +911,17 @@ impl TurboTasks { id: TaskId, consistency: ReadConsistency, ) -> Result<()> { - read_task_output( - self, - id, - ReadOutputOptions { - // INVALIDATION: This doesn't return a value, only waits for it to be ready. - tracking: ReadTracking::Untracked, - consistency, - }, - ) - .await?; - Ok(()) + let options = ReadOutputOptions { + // INVALIDATION: This doesn't return a value, only waits for it to be ready. + tracking: ReadTracking::Untracked, + consistency, + }; + loop { + match ::try_read_task_output(self, id, options)? { + Ok(_) => return Ok(()), + Err(listener) => listener.await, + } + } } /// Returns [UpdateInfo] with all updates aggregated over a given duration @@ -1012,7 +1021,7 @@ impl TurboTasks { } pub async fn stop_and_wait(&self) { - turbo_tasks_future_scope(self.pin(), async move { + turbo_tasks_future_scope(self.make_handle_from_ref(), async move { self.backend.stopping(self); self.stopped.store(true, Ordering::Release); { @@ -1052,7 +1061,7 @@ impl TurboTasks { self.begin_background_job(); tokio::spawn( TURBO_TASKS - .scope(this.clone(), async move { + .scope(this.clone().make_handle(), async move { if !this.stopped.load(Ordering::Acquire) { this = func(this).await; } @@ -1200,7 +1209,11 @@ impl Executor, ScheduledTask, TaskPriority> for TurboT .await }; - Either::Left(TURBO_TASKS.scope(this2, future).instrument(span)) + Either::Left( + TURBO_TASKS + .scope(this2.make_handle(), future) + .instrument(span), + ) } ScheduledTask::LocalTask { ty, @@ -1267,7 +1280,11 @@ impl Executor, ScheduledTask, TaskPriority> for TurboT }; let future = CURRENT_TASK_STATE.scope(global_task_state, future); - Either::Right(TURBO_TASKS.scope(this2, future).instrument(span)) + Either::Right( + TURBO_TASKS + .scope(this2.make_handle(), future) + .instrument(span), + ) } } } @@ -1312,24 +1329,6 @@ impl TurboTasksCallApi for TurboTasks { self.trait_call(trait_method, this, arg, persistence) } - #[track_caller] - fn run( - &self, - future: Pin> + Send + 'static>>, - ) -> Pin> + Send>> { - let this = self.pin(); - Box::pin(async move { this.run(future).await }) - } - - #[track_caller] - fn run_once( - &self, - future: Pin> + Send + 'static>>, - ) -> Pin> + Send>> { - let this = self.pin(); - Box::pin(async move { this.run_once(future).await }) - } - #[track_caller] fn run_once_with_reason( &self, @@ -1550,13 +1549,6 @@ impl TurboTasksApi for TurboTasks { self.backend.task_statistics() } - fn stop_and_wait(&self) -> Pin + Send + 'static>> { - let this = self.pin(); - Box::pin(async move { - this.stop_and_wait().await; - }) - } - fn subscribe_to_compilation_events( &self, event_types: Option>, @@ -1640,61 +1632,6 @@ pub(crate) fn debug_assert_not_in_top_level_task(operation: &str) { } } -pub async fn run( - tt: Arc, - future: impl Future> + Send + 'static, -) -> Result { - let (tx, rx) = tokio::sync::oneshot::channel(); - - tt.run(Box::pin(async move { - let result = future.await?; - tx.send(result) - .map_err(|_| anyhow!("unable to send result"))?; - Ok(()) - })) - .await?; - - Ok(rx.await?) -} - -pub async fn run_once( - tt: Arc, - future: impl Future> + Send + 'static, -) -> Result { - let (tx, rx) = tokio::sync::oneshot::channel(); - - tt.run_once(Box::pin(async move { - let result = future.await?; - tx.send(result) - .map_err(|_| anyhow!("unable to send result"))?; - Ok(()) - })) - .await?; - - Ok(rx.await?) -} - -pub async fn run_once_with_reason( - tt: Arc, - reason: impl InvalidationReason, - future: impl Future> + Send + 'static, -) -> Result { - let (tx, rx) = tokio::sync::oneshot::channel(); - - tt.run_once_with_reason( - (Arc::new(reason) as Arc).into(), - Box::pin(async move { - let result = future.await?; - tx.send(result) - .map_err(|_| anyhow!("unable to send result"))?; - Ok(()) - }), - ) - .await?; - - Ok(rx.await?) -} - /// Calls [`TurboTasks::dynamic_call`] for the current turbo tasks instance. pub fn dynamic_call( func: &'static NativeFunction, @@ -1715,28 +1652,28 @@ pub fn trait_call( with_turbo_tasks(|tt| tt.trait_call(trait_method, this, arg, persistence)) } -pub fn turbo_tasks() -> Arc { - TURBO_TASKS.with(|arc| arc.clone()) +pub fn turbo_tasks() -> TurboTasksHandle { + TURBO_TASKS.with(|h| h.clone()) } -pub fn turbo_tasks_weak() -> Weak { - TURBO_TASKS.with(Arc::downgrade) +pub fn turbo_tasks_weak() -> crate::TurboTasksWeakHandle { + TURBO_TASKS.with(|h| h.downgrade()) } -pub fn try_turbo_tasks() -> Option> { - TURBO_TASKS.try_with(|arc| arc.clone()).ok() +pub fn try_turbo_tasks() -> Option { + TURBO_TASKS.try_with(|h| h.clone()).ok() } -pub fn with_turbo_tasks(func: impl FnOnce(&Arc) -> T) -> T { - TURBO_TASKS.with(|arc| func(arc)) +pub fn with_turbo_tasks(func: impl FnOnce(&TurboTasksHandle) -> T) -> T { + TURBO_TASKS.with(|h| func(h)) } -pub fn turbo_tasks_scope(tt: Arc, f: impl FnOnce() -> T) -> T { +pub fn turbo_tasks_scope(tt: TurboTasksHandle, f: impl FnOnce() -> T) -> T { TURBO_TASKS.sync_scope(tt, f) } pub fn turbo_tasks_future_scope( - tt: Arc, + tt: TurboTasksHandle, f: impl Future, ) -> impl Future { TURBO_TASKS.scope(tt, f) @@ -1850,19 +1787,6 @@ pub fn emit(collectible: ResolvedVc) { }) } -pub(crate) async fn read_task_output( - this: &dyn TurboTasksApi, - id: TaskId, - options: ReadOutputOptions, -) -> Result { - loop { - match this.try_read_task_output(id, options)? { - Ok(result) => return Ok(result), - Err(listener) => listener.await, - } - } -} - /// A reference to a task's cell with methods that allow updating the contents /// of the cell. /// @@ -2179,7 +2103,7 @@ pub fn find_cell_by_id(ty: ValueTypeId) -> CurrentCellRef { } pub(crate) async fn read_local_output( - this: &dyn TurboTasksApi, + this: &TurboTasksHandle, execution_id: ExecutionId, local_task_id: LocalTaskId, ) -> Result { diff --git a/turbopack/crates/turbo-tasks/src/raw_vc.rs b/turbopack/crates/turbo-tasks/src/raw_vc.rs index 397653d8c4b8..17878b6dc58e 100644 --- a/turbopack/crates/turbo-tasks/src/raw_vc.rs +++ b/turbopack/crates/turbo-tasks/src/raw_vc.rs @@ -2,7 +2,6 @@ use std::{ fmt::{Debug, Display}, future::Future, pin::Pin, - sync::Arc, task::{Poll, ready}, }; @@ -13,7 +12,7 @@ use serde::{Deserialize, Serialize}; use crate::{ CollectiblesSource, ReadCellOptions, ReadConsistency, ReadOutputOptions, ResolvedVc, TaskId, - TaskPersistence, TraitTypeId, ValueTypeId, VcValueTrait, + TaskPersistence, TraitTypeId, TurboTasksHandle, ValueTypeId, VcValueTrait, backend::TypedCellContent, event::EventListener, id::{ExecutionId, LocalTaskId}, @@ -152,7 +151,7 @@ impl RawVc { Ok(match self { RawVc::LocalOutput(execution_id, local_task_id, ..) => { let tt = turbo_tasks(); - let local_output = read_local_output(&*tt, execution_id, local_task_id).await?; + let local_output = read_local_output(&tt, execution_id, local_task_id).await?; debug_assert!( !matches!(local_output, RawVc::LocalOutput(_, _, _)), "a LocalOutput cannot point at other LocalOutputs" @@ -349,7 +348,7 @@ impl Future for ResolveRawVcFuture { // SAFETY: we are not moving self let this = unsafe { self.get_unchecked_mut() }; - let poll_fn = |tt: &Arc| -> Poll { + let poll_fn = |tt: &TurboTasksHandle| -> Poll { 'outer: loop { ready!(poll_listener(&mut this.listener, cx)); let listener = match this.current { @@ -486,7 +485,7 @@ impl Future for ReadRawVcFuture { // At this point `this.resolved` is `Some((task, index))`. let (task, index) = this.resolved.unwrap(); - let poll_fn = |tt: &Arc| -> Poll { + let poll_fn = |tt: &TurboTasksHandle| -> Poll { loop { ready!(poll_listener(&mut this.listener, cx)); let listener = match tt.try_read_task_cell(task, index, this.read_cell_options) { diff --git a/turbopack/crates/turbo-tasks/src/scope.rs b/turbopack/crates/turbo-tasks/src/scope.rs index 6fe7efc46120..cd3f6183ac34 100644 --- a/turbopack/crates/turbo-tasks/src/scope.rs +++ b/turbopack/crates/turbo-tasks/src/scope.rs @@ -17,7 +17,7 @@ use parking_lot::{Condvar, Mutex}; use tokio::{runtime::Handle, task::block_in_place}; use tracing::{Span, info_span}; -use crate::{TurboTasksApi, manager::try_turbo_tasks, turbo_tasks_scope}; +use crate::{manager::try_turbo_tasks, turbo_tasks_scope}; /// Number of worker tasks to spawn that process jobs. It's 1 less than the number of cpus as we /// also use the current task as worker. @@ -153,7 +153,7 @@ pub struct Scope<'scope, 'env: 'scope, R: Send + 'env> { index: AtomicUsize, inner: Arc, handle: Handle, - turbo_tasks: Option>, + turbo_tasks: Option, span: Span, /// Invariance over 'env, to make sure 'env cannot shrink, which is necessary for soundness. /// diff --git a/turbopack/crates/turbo-tasks/src/state.rs b/turbopack/crates/turbo-tasks/src/state.rs index 6b2d49b5500c..399ca6013922 100644 --- a/turbopack/crates/turbo-tasks/src/state.rs +++ b/turbopack/crates/turbo-tasks/src/state.rs @@ -85,7 +85,7 @@ fn notify_mutated( let _span = trace_span!("state value changed").entered(); with_turbo_tasks(|tt| { for invalidator in invalidators { - invalidator.invalidate(&**tt); + invalidator.invalidate(tt); } if let Some(serialization_invalidator) = serialization_invalidator { tt.invalidate_serialization(serialization_invalidator.task()); diff --git a/turbopack/crates/turbopack-analyze/src/lib.rs b/turbopack/crates/turbopack-analyze/src/lib.rs index 69d13321b81a..f8b23e922899 100644 --- a/turbopack/crates/turbopack-analyze/src/lib.rs +++ b/turbopack/crates/turbopack-analyze/src/lib.rs @@ -1,4 +1,10 @@ #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary. The backend is a regular dep but rustc +// only adds rlibs to the link command for crates Rust code references. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub mod compressed_size; pub mod split_chunk; diff --git a/turbopack/crates/turbopack-bench/Cargo.toml b/turbopack/crates/turbopack-bench/Cargo.toml index 4432d8dacc94..db283ec74cf6 100644 --- a/turbopack/crates/turbopack-bench/Cargo.toml +++ b/turbopack/crates/turbopack-bench/Cargo.toml @@ -34,9 +34,14 @@ tempfile = { workspace = true } tokio = { workspace = true, features = ["full"] } tungstenite = { workspace = true } turbo-tasks = { workspace = true } -turbo-tasks-testing = { workspace = true } turbopack-create-test-app = { workspace = true } url = { workspace = true } +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test/bench binaries. See the matching +# `extern crate` references in `src/lib.rs` and `benches/mod.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } + [target.'cfg(unix)'.dependencies] nix = { version="0.30.1", features=["signal"] } diff --git a/turbopack/crates/turbopack-bench/benches/mod.rs b/turbopack/crates/turbopack-bench/benches/mod.rs index 56b6c57d3ac5..1eb652006398 100644 --- a/turbopack/crates/turbopack-bench/benches/mod.rs +++ b/turbopack/crates/turbopack-bench/benches/mod.rs @@ -1,3 +1,7 @@ +// Force linking the static-dispatch providers — see the matching +// dev-dep in `Cargo.toml`. +extern crate turbo_tasks_backend; + use criterion::{Criterion, criterion_group, criterion_main}; use turbopack_bench::bundlers::Bundler; diff --git a/turbopack/crates/turbopack-bench/src/lib.rs b/turbopack/crates/turbopack-bench/src/lib.rs index 8f27940201ab..f77482f80242 100644 --- a/turbopack/crates/turbopack-bench/src/lib.rs +++ b/turbopack/crates/turbopack-bench/src/lib.rs @@ -1,3 +1,8 @@ +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + use std::{ fs::{self}, panic::AssertUnwindSafe, diff --git a/turbopack/crates/turbopack-bench/src/util/mod.rs b/turbopack/crates/turbopack-bench/src/util/mod.rs index 460f092f8860..30ae356e8fa2 100644 --- a/turbopack/crates/turbopack-bench/src/util/mod.rs +++ b/turbopack/crates/turbopack-bench/src/util/mod.rs @@ -20,12 +20,14 @@ pub use prepared_app::PreparedApp; use regex::Regex; use tungstenite::{Error::Protocol, error::ProtocolError::ResetWithoutClosingHandshake}; use turbo_tasks::util::FormatDuration; -use turbo_tasks_testing::retry::{retry, retry_async}; use turbopack_create_test_app::test_app_builder::{ EffectMode, PackageJsonConfig, TestApp, TestAppBuilder, }; -use self::env::read_env_bool; +use self::{ + env::read_env_bool, + retry::{retry, retry_async}, +}; use crate::bundlers::{Bundler, RenderType}; pub mod env; @@ -33,6 +35,7 @@ pub mod module_picker; pub mod npm; mod page_guard; mod prepared_app; +mod retry; pub const BINDING_NAME: &str = "__turbopackBenchBinding"; diff --git a/turbopack/crates/turbo-tasks-testing/src/retry.rs b/turbopack/crates/turbopack-bench/src/util/retry.rs similarity index 100% rename from turbopack/crates/turbo-tasks-testing/src/retry.rs rename to turbopack/crates/turbopack-bench/src/util/retry.rs diff --git a/turbopack/crates/turbopack-browser/Cargo.toml b/turbopack/crates/turbopack-browser/Cargo.toml index c4a6fb22ffdc..eb453d206500 100644 --- a/turbopack/crates/turbopack-browser/Cargo.toml +++ b/turbopack/crates/turbopack-browser/Cargo.toml @@ -41,3 +41,9 @@ turbopack-ecmascript = { workspace = true } turbopack-ecmascript-runtime = { workspace = true } turbopack-resolve = { workspace = true } + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching +# `extern crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbopack-browser/src/lib.rs b/turbopack/crates/turbopack-browser/src/lib.rs index 4f8f9b765828..f228fbe1507c 100644 --- a/turbopack/crates/turbopack-browser/src/lib.rs +++ b/turbopack/crates/turbopack-browser/src/lib.rs @@ -1,6 +1,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub(crate) mod chunking_context; pub mod ecmascript; pub mod react_refresh; diff --git a/turbopack/crates/turbopack-cli-utils/Cargo.toml b/turbopack/crates/turbopack-cli-utils/Cargo.toml index 2cae973901ef..66e75cc7b4fd 100644 --- a/turbopack/crates/turbopack-cli-utils/Cargo.toml +++ b/turbopack/crates/turbopack-cli-utils/Cargo.toml @@ -26,3 +26,9 @@ turbo-tasks-fs = { workspace = true } turbopack-core = { workspace = true } turbopack-resolve = { workspace = true } +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching `extern +# crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } + diff --git a/turbopack/crates/turbopack-cli-utils/src/lib.rs b/turbopack/crates/turbopack-cli-utils/src/lib.rs index 96b679fe1bb1..daad4390d855 100644 --- a/turbopack/crates/turbopack-cli-utils/src/lib.rs +++ b/turbopack/crates/turbopack-cli-utils/src/lib.rs @@ -2,6 +2,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub mod issue; pub mod runtime_entry; pub mod source_context; diff --git a/turbopack/crates/turbopack-cli/benches/mod.rs b/turbopack/crates/turbopack-cli/benches/mod.rs index 0fc2595b6158..55dd085ec363 100644 --- a/turbopack/crates/turbopack-cli/benches/mod.rs +++ b/turbopack/crates/turbopack-cli/benches/mod.rs @@ -1,3 +1,7 @@ +// Force linking the static-dispatch providers — see the matching +// dep declaration on `turbo-tasks-backend` in `Cargo.toml`. +extern crate turbo_tasks_backend; + use criterion::{Criterion, criterion_group, criterion_main}; use turbopack_bench::bundlers::Bundler; diff --git a/turbopack/crates/turbopack-css/Cargo.toml b/turbopack/crates/turbopack-css/Cargo.toml index 9efa0bf57683..44cb124da386 100644 --- a/turbopack/crates/turbopack-css/Cargo.toml +++ b/turbopack/crates/turbopack-css/Cargo.toml @@ -38,3 +38,9 @@ turbopack-core = { workspace = true } turbopack-ecmascript = { workspace = true } urlencoding = { workspace = true } + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching +# `extern crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbopack-css/src/lib.rs b/turbopack/crates/turbopack-css/src/lib.rs index 513fb17a146f..ed65e4955da8 100644 --- a/turbopack/crates/turbopack-css/src/lib.rs +++ b/turbopack/crates/turbopack-css/src/lib.rs @@ -2,6 +2,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + mod asset; pub mod chunk; mod code_gen; diff --git a/turbopack/crates/turbopack-dev-server/src/lib.rs b/turbopack/crates/turbopack-dev-server/src/lib.rs index fa9231d652f9..46e06823a053 100644 --- a/turbopack/crates/turbopack-dev-server/src/lib.rs +++ b/turbopack/crates/turbopack-dev-server/src/lib.rs @@ -19,7 +19,7 @@ use std::{ time::{Duration, Instant}, }; -use anyhow::{Context, Result}; +use anyhow::{Context, Result, anyhow}; use hyper::{ Request, Response, Server, server::{Builder, conn::AddrIncoming}, @@ -30,7 +30,7 @@ use socket2::{Domain, Protocol, Socket, Type}; use tokio::task::JoinHandle; use tracing::{Instrument, Level, Span, event, info_span}; use turbo_tasks::{ - Effects, NonLocalValue, OperationVc, PrettyPrintError, TurboTasksApi, Vc, run_once_with_reason, + Effects, InvalidationReason, NonLocalValue, OperationVc, PrettyPrintError, TurboTasksApi, Vc, take_effects, trace::TraceRawVcs, util::FormatDuration, }; use turbopack_core::issue::{IssueReporter, IssueSeverity, handle_issues}; @@ -122,12 +122,15 @@ impl DevServer { } impl DevServerBuilder { - pub fn serve( + pub fn serve( self, - turbo_tasks: Arc, + turbo_tasks: Arc, source_provider: impl SourceProvider + NonLocalValue + TraceRawVcs + Sync, get_issue_reporter: Arc Vc> + Send + Sync>, - ) -> DevServer { + ) -> DevServer + where + T: TurboTasksApi + ?Sized + 'static, + { let ongoing_side_effects = Arc::new(Mutex::new(VecDeque::< Arc>>>>, >::with_capacity(16))); @@ -181,103 +184,133 @@ impl DevServerBuilder { method: request.method().clone(), uri: request.uri().clone(), }; - run_once_with_reason(tt.clone(), reason, async move { - // TODO: `get_issue_reporter` should be an `OperationVc`, as there's a - // risk it could be a task-local Vc, which is not safe for us to await. - let issue_reporter = get_issue_reporter(); + let (response_tx, response_rx) = + tokio::sync::oneshot::channel::>(); + let request_tt = tt.clone(); + let request_inner = async move { + let result: Result> = async move { + // TODO: `get_issue_reporter` should be an `OperationVc`, as there's + // a risk it could be a task-local + // Vc, which is not safe for us to await. + let issue_reporter = get_issue_reporter(); - if hyper_tungstenite::is_upgrade_request(&request) { - let uri = request.uri(); - let path = uri.path(); + if hyper_tungstenite::is_upgrade_request(&request) { + let uri = request.uri(); + let path = uri.path(); - if path == "/turbopack-hmr" { - let (response, websocket) = - hyper_tungstenite::upgrade(request, None)?; - let update_server = - UpdateServer::new(source_provider, issue_reporter); - update_server.run(&*tt, websocket); - return Ok(response); - } + if path == "/turbopack-hmr" { + let (response, websocket) = + hyper_tungstenite::upgrade(request, None)?; + let update_server = + UpdateServer::new(source_provider, issue_reporter); + update_server.run(&tt, websocket); + return Ok(response); + } - println!("[404] {path} (WebSocket)"); - if path == "/_next/hmr" { - // Special-case requests to hmr as these are made by - // Next.js clients built - // without turbopack, which may be making requests in - // development. - println!( - "A non-turbopack next.js client is trying to connect." - ); - println!( - "Make sure to reload/close any browser window which has \ - been opened without --turbo." - ); - } + println!("[404] {path} (WebSocket)"); + if path == "/_next/hmr" { + // Special-case requests to hmr as these are made by + // Next.js clients built + // without turbopack, which may be making requests in + // development. + println!( + "A non-turbopack next.js client is trying to connect." + ); + println!( + "Make sure to reload/close any browser window which \ + has been opened without --turbo." + ); + } - return Ok(Response::builder() - .status(404) - .body(hyper::Body::empty())?); - } + return Ok(Response::builder() + .status(404) + .body(hyper::Body::empty())?); + } - let uri = request.uri(); - let path = uri.path().to_string(); - let source_with_issues_op = - get_source_with_issues_operation(source_provider.get_source()); - let ContentSourceWithIssues { source_op, effects } = - &*source_with_issues_op.read_strongly_consistent().await?; - effects.apply().await?; - handle_issues( - source_with_issues_op, - issue_reporter, - IssueSeverity::Fatal, - Some(&path), - Some("get source"), - ) - .await?; - let (response, side_effects) = - http::process_request_with_content_source( - // HACK: pass `source` here (instead of `resolved_source` - // because the underlying API wants to do it's own - // `.resolve().strongly_consistent()` call. - // - // It's unlikely (the calls happen one-after-another), but this - // could cause inconsistency between the reported issues and - // the generated HTTP response. - *source_op, - request, + let uri = request.uri(); + let path = uri.path().to_string(); + let source_with_issues_op = + get_source_with_issues_operation(source_provider.get_source()); + let ContentSourceWithIssues { source_op, effects } = + &*source_with_issues_op.read_strongly_consistent().await?; + effects.apply().await?; + handle_issues( + source_with_issues_op, issue_reporter, + IssueSeverity::Fatal, + Some(&path), + Some("get source"), ) .await?; - let status = response.status().as_u16(); - let is_error = response.status().is_client_error() - || response.status().is_server_error(); - let elapsed = start.elapsed(); - if is_error - || (cfg!(feature = "log_request_stats") - && elapsed > Duration::from_secs(1)) - { - println!( - "[{status}] {path} ({duration})", - duration = FormatDuration(elapsed) - ); + let (response, side_effects) = + http::process_request_with_content_source( + // HACK: pass `source` here (instead of `resolved_source` + // because the underlying API wants to do it's own + // `.resolve().strongly_consistent()` call. + // + // It's unlikely (the calls happen one-after-another), but + // this could cause + // inconsistency between the reported issues and + // the generated HTTP response. + *source_op, + request, + issue_reporter, + ) + .await?; + let status = response.status().as_u16(); + let is_error = response.status().is_client_error() + || response.status().is_server_error(); + let elapsed = start.elapsed(); + if is_error + || (cfg!(feature = "log_request_stats") + && elapsed > Duration::from_secs(1)) + { + println!( + "[{status}] {path} ({duration})", + duration = FormatDuration(elapsed) + ); + } + if !side_effects.is_empty() { + let side_effects_tt = tt.clone(); + let join_handle = tokio::spawn(async move { + side_effects_tt + .run_once_with_reason( + (Arc::new(side_effects_reason) + as Arc) + .into(), + Box::pin(async move { + for side_effect in side_effects { + side_effect.apply().await?; + } + Ok(()) + }), + ) + .await + }); + ongoing_side_effects.lock().push_back(Arc::new( + tokio::sync::Mutex::new(Some(join_handle)), + )); + } + Ok(response) } - if !side_effects.is_empty() { - let join_handle = tokio::spawn(run_once_with_reason( - tt.clone(), - side_effects_reason, - async move { - for side_effect in side_effects { - side_effect.apply().await?; - } + .await; + result + }; + async move { + request_tt + .run_once_with_reason( + (Arc::new(reason) as Arc).into(), + Box::pin(async move { + let response = request_inner.await?; + let _ = response_tx.send(response); Ok(()) - }, - )); - ongoing_side_effects.lock().push_back(Arc::new( - tokio::sync::Mutex::new(Some(join_handle)), - )); - } - Ok(response) - }) + }), + ) + .await?; + response_rx + .await + .map_err(|_| anyhow!("response channel closed")) + } .await }; async move { diff --git a/turbopack/crates/turbopack-dev-server/src/update/server.rs b/turbopack/crates/turbopack-dev-server/src/update/server.rs index 60390e28794e..49b1b87ff316 100644 --- a/turbopack/crates/turbopack-dev-server/src/update/server.rs +++ b/turbopack/crates/turbopack-dev-server/src/update/server.rs @@ -1,6 +1,7 @@ use std::{ ops::ControlFlow, pin::Pin, + sync::Arc, task::{Context, Poll}, }; @@ -13,8 +14,8 @@ use tokio::select; use tokio_stream::StreamMap; use tracing::{Level, instrument}; use turbo_tasks::{ - NonLocalValue, OperationVc, PrettyPrintError, ReadRef, TransientInstance, TurboTasksApi, Vc, - trace::TraceRawVcs, + NonLocalValue, OperationVc, PrettyPrintError, ReadRef, TransientInstance, TurboTasksCallApi, + Vc, trace::TraceRawVcs, }; use turbo_tasks_fs::json::parse_json_with_source_context; use turbopack_core::{issue::IssueReporter, version::Update}; @@ -52,7 +53,10 @@ where } /// Run the update server loop. - pub fn run(self, tt: &dyn TurboTasksApi, ws: HyperWebsocket) { + pub fn run(self, tt: &Arc, ws: HyperWebsocket) + where + T: TurboTasksCallApi + ?Sized + 'static, + { tt.start_once_process(Box::pin(async move { if let Err(err) = self.run_internal(ws).await { println!("[UpdateServer]: error {err:#}"); diff --git a/turbopack/crates/turbopack-ecmascript-hmr-protocol/Cargo.toml b/turbopack/crates/turbopack-ecmascript-hmr-protocol/Cargo.toml index 08a5893c1cee..8602cfbc359b 100644 --- a/turbopack/crates/turbopack-ecmascript-hmr-protocol/Cargo.toml +++ b/turbopack/crates/turbopack-ecmascript-hmr-protocol/Cargo.toml @@ -19,3 +19,9 @@ serde_json = { workspace = true } turbo-rcstr = { workspace = true } turbopack-cli-utils = { workspace = true } turbopack-core = { workspace = true } + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching `extern +# crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbopack-ecmascript-hmr-protocol/src/lib.rs b/turbopack/crates/turbopack-ecmascript-hmr-protocol/src/lib.rs index 95fc8213b4e1..bba61e47df57 100644 --- a/turbopack/crates/turbopack-ecmascript-hmr-protocol/src/lib.rs +++ b/turbopack/crates/turbopack-ecmascript-hmr-protocol/src/lib.rs @@ -1,3 +1,8 @@ +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + use std::{collections::BTreeMap, fmt::Display, path::PathBuf}; use serde::{Deserialize, Serialize}; diff --git a/turbopack/crates/turbopack-ecmascript-plugins/Cargo.toml b/turbopack/crates/turbopack-ecmascript-plugins/Cargo.toml index 66621d88574a..ef92d79becb1 100644 --- a/turbopack/crates/turbopack-ecmascript-plugins/Cargo.toml +++ b/turbopack/crates/turbopack-ecmascript-plugins/Cargo.toml @@ -45,3 +45,9 @@ swc_plugin_backend_wasmtime = { workspace = true } swc_emotion = { workspace = true } swc_relay = { workspace = true } + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching +# `extern crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbopack-ecmascript-plugins/src/lib.rs b/turbopack/crates/turbopack-ecmascript-plugins/src/lib.rs index 728803123364..fcafb6688dc7 100644 --- a/turbopack/crates/turbopack-ecmascript-plugins/src/lib.rs +++ b/turbopack/crates/turbopack-ecmascript-plugins/src/lib.rs @@ -1,4 +1,9 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub mod transform; diff --git a/turbopack/crates/turbopack-ecmascript-runtime/Cargo.toml b/turbopack/crates/turbopack-ecmascript-runtime/Cargo.toml index 092b611ec866..454494ef2255 100644 --- a/turbopack/crates/turbopack-ecmascript-runtime/Cargo.toml +++ b/turbopack/crates/turbopack-ecmascript-runtime/Cargo.toml @@ -28,3 +28,9 @@ turbo-tasks = { workspace = true } turbo-tasks-fs = { workspace = true } turbopack-core = { workspace = true } turbopack-ecmascript = { workspace = true } + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching +# `extern crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbopack-ecmascript-runtime/src/lib.rs b/turbopack/crates/turbopack-ecmascript-runtime/src/lib.rs index 13c5b544a6fa..175488d5b64a 100644 --- a/turbopack/crates/turbopack-ecmascript-runtime/src/lib.rs +++ b/turbopack/crates/turbopack-ecmascript-runtime/src/lib.rs @@ -1,6 +1,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub(crate) mod browser_runtime; #[cfg(feature = "test")] pub(crate) mod dummy_runtime; diff --git a/turbopack/crates/turbopack-env/Cargo.toml b/turbopack/crates/turbopack-env/Cargo.toml index ccec06b934ab..970fa0dcecec 100644 --- a/turbopack/crates/turbopack-env/Cargo.toml +++ b/turbopack/crates/turbopack-env/Cargo.toml @@ -22,3 +22,9 @@ turbo-tasks-fs = { workspace = true } turbopack-core = { workspace = true } turbopack-ecmascript = { workspace = true } + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching +# `extern crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbopack-env/src/lib.rs b/turbopack/crates/turbopack-env/src/lib.rs index 89d1cc6424be..ebdb48fddacf 100644 --- a/turbopack/crates/turbopack-env/src/lib.rs +++ b/turbopack/crates/turbopack-env/src/lib.rs @@ -13,6 +13,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + mod asset; pub mod dotenv; mod embeddable; diff --git a/turbopack/crates/turbopack-image/Cargo.toml b/turbopack/crates/turbopack-image/Cargo.toml index ebdab94db4af..bc8d0c0b9499 100644 --- a/turbopack/crates/turbopack-image/Cargo.toml +++ b/turbopack/crates/turbopack-image/Cargo.toml @@ -41,3 +41,9 @@ turbo-tasks = { workspace = true } turbo-tasks-fs = { workspace = true } turbopack-core = { workspace = true } +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching `extern +# crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } + diff --git a/turbopack/crates/turbopack-image/src/lib.rs b/turbopack/crates/turbopack-image/src/lib.rs index 4177d349e030..26b83fc23a21 100644 --- a/turbopack/crates/turbopack-image/src/lib.rs +++ b/turbopack/crates/turbopack-image/src/lib.rs @@ -1,4 +1,9 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub mod process; diff --git a/turbopack/crates/turbopack-mdx/Cargo.toml b/turbopack/crates/turbopack-mdx/Cargo.toml index 029d5ab6f771..29c0574e7719 100644 --- a/turbopack/crates/turbopack-mdx/Cargo.toml +++ b/turbopack/crates/turbopack-mdx/Cargo.toml @@ -23,3 +23,9 @@ turbo-tasks = { workspace = true } turbo-tasks-fs = { workspace = true } turbopack-core = { workspace = true } turbopack-ecmascript = { workspace = true } + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching +# `extern crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbopack-mdx/src/lib.rs b/turbopack/crates/turbopack-mdx/src/lib.rs index 9e1d955f5e18..4d4fbccf4d30 100644 --- a/turbopack/crates/turbopack-mdx/src/lib.rs +++ b/turbopack/crates/turbopack-mdx/src/lib.rs @@ -2,6 +2,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + use anyhow::Result; use async_trait::async_trait; use mdxjs::{MdxParseOptions, Options, compile}; diff --git a/turbopack/crates/turbopack-nft/src/lib.rs b/turbopack/crates/turbopack-nft/src/lib.rs index 53f5ca6c1703..7fa61daa2779 100644 --- a/turbopack/crates/turbopack-nft/src/lib.rs +++ b/turbopack/crates/turbopack-nft/src/lib.rs @@ -1,3 +1,9 @@ #![feature(arbitrary_self_types)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary. The backend is a regular dep but rustc +// only adds rlibs to the link command for crates Rust code references. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub mod nft; diff --git a/turbopack/crates/turbopack-node/src/lib.rs b/turbopack/crates/turbopack-node/src/lib.rs index bead5baf633e..428f6d266602 100644 --- a/turbopack/crates/turbopack-node/src/lib.rs +++ b/turbopack/crates/turbopack-node/src/lib.rs @@ -2,6 +2,12 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary. The backend is a regular dep but rustc +// only adds rlibs to the link command for crates Rust code references. +#[cfg(test)] +extern crate turbo_tasks_backend; + use anyhow::Result; use rustc_hash::FxHashMap; use turbo_tasks::{ResolvedVc, TryFlatJoinIterExt, Vc}; diff --git a/turbopack/crates/turbopack-nodejs/Cargo.toml b/turbopack/crates/turbopack-nodejs/Cargo.toml index 4e1b876dcfbe..b891b1762f1f 100644 --- a/turbopack/crates/turbopack-nodejs/Cargo.toml +++ b/turbopack/crates/turbopack-nodejs/Cargo.toml @@ -39,3 +39,9 @@ turbopack-core = { workspace = true } turbopack-ecmascript = { workspace = true } turbopack-ecmascript-runtime = { workspace = true } + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching +# `extern crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbopack-nodejs/src/lib.rs b/turbopack/crates/turbopack-nodejs/src/lib.rs index 70f72ef4ec57..80d0144fd2f5 100644 --- a/turbopack/crates/turbopack-nodejs/src/lib.rs +++ b/turbopack/crates/turbopack-nodejs/src/lib.rs @@ -1,6 +1,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub(crate) mod chunking_context; pub(crate) mod ecmascript; diff --git a/turbopack/crates/turbopack-resolve/Cargo.toml b/turbopack/crates/turbopack-resolve/Cargo.toml index 244bb74a41ba..f43fdf2f962b 100644 --- a/turbopack/crates/turbopack-resolve/Cargo.toml +++ b/turbopack/crates/turbopack-resolve/Cargo.toml @@ -26,3 +26,9 @@ turbo-tasks-fs = { workspace = true } turbopack-core = { workspace = true } next-taskless = { workspace = true } +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching `extern +# crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } + diff --git a/turbopack/crates/turbopack-resolve/src/lib.rs b/turbopack/crates/turbopack-resolve/src/lib.rs index 93a3a21f1185..586fc02f3703 100644 --- a/turbopack/crates/turbopack-resolve/src/lib.rs +++ b/turbopack/crates/turbopack-resolve/src/lib.rs @@ -1,6 +1,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub mod ecmascript; pub mod node_native_binding; pub mod resolve; diff --git a/turbopack/crates/turbopack-static/Cargo.toml b/turbopack/crates/turbopack-static/Cargo.toml index 8fc528b84d3e..f132fd657aa5 100644 --- a/turbopack/crates/turbopack-static/Cargo.toml +++ b/turbopack/crates/turbopack-static/Cargo.toml @@ -23,3 +23,9 @@ turbopack-core = { workspace = true } turbopack-css = { workspace = true } turbopack-ecmascript = { workspace = true } + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching +# `extern crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbopack-static/src/lib.rs b/turbopack/crates/turbopack-static/src/lib.rs index ac57de132f2d..7096a289cbd2 100644 --- a/turbopack/crates/turbopack-static/src/lib.rs +++ b/turbopack/crates/turbopack-static/src/lib.rs @@ -12,6 +12,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub mod css; pub mod ecma; pub mod fixed; diff --git a/turbopack/crates/turbopack-swc-utils/Cargo.toml b/turbopack/crates/turbopack-swc-utils/Cargo.toml index 3a9a8bfe68aa..65335b4681e9 100644 --- a/turbopack/crates/turbopack-swc-utils/Cargo.toml +++ b/turbopack/crates/turbopack-swc-utils/Cargo.toml @@ -26,3 +26,9 @@ swc_core = { workspace = true, features = [ "common_sourcemap", ] } +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching `extern +# crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } + diff --git a/turbopack/crates/turbopack-swc-utils/src/lib.rs b/turbopack/crates/turbopack-swc-utils/src/lib.rs index 3f961f3b4c4c..59a4cb8eb54c 100644 --- a/turbopack/crates/turbopack-swc-utils/src/lib.rs +++ b/turbopack/crates/turbopack-swc-utils/src/lib.rs @@ -1,4 +1,9 @@ #![feature(min_specialization)] #![feature(str_split_remainder)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub mod emitter; diff --git a/turbopack/crates/turbopack-test-utils/Cargo.toml b/turbopack/crates/turbopack-test-utils/Cargo.toml index ce4cbba4346f..b664f6b26ec3 100644 --- a/turbopack/crates/turbopack-test-utils/Cargo.toml +++ b/turbopack/crates/turbopack-test-utils/Cargo.toml @@ -26,3 +26,9 @@ turbopack-cli-utils = { workspace = true } turbopack-core = { workspace = true } rustc-hash = { workspace = true } +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching `extern +# crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } + diff --git a/turbopack/crates/turbopack-test-utils/src/lib.rs b/turbopack/crates/turbopack-test-utils/src/lib.rs index 3aead34dd5e4..ddb7d914d532 100644 --- a/turbopack/crates/turbopack-test-utils/src/lib.rs +++ b/turbopack/crates/turbopack-test-utils/src/lib.rs @@ -2,6 +2,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + pub mod jest; pub mod noop_asset_context; pub mod snapshot; diff --git a/turbopack/crates/turbopack-wasm/Cargo.toml b/turbopack/crates/turbopack-wasm/Cargo.toml index 5156042b7689..346cff980c36 100644 --- a/turbopack/crates/turbopack-wasm/Cargo.toml +++ b/turbopack/crates/turbopack-wasm/Cargo.toml @@ -26,3 +26,9 @@ turbopack-ecmascript = { workspace = true } wasmparser = "0.235.0" wat = "1.0.69" + +# Dev-only link of `turbo-tasks-backend` so the `__tt_static_*` extern +# symbols resolve in this crate's test binary. See the matching +# `extern crate` in `src/lib.rs`. +[dev-dependencies] +turbo-tasks-backend = { workspace = true } diff --git a/turbopack/crates/turbopack-wasm/src/lib.rs b/turbopack/crates/turbopack-wasm/src/lib.rs index df2e799be8bb..134824d3a1d1 100644 --- a/turbopack/crates/turbopack-wasm/src/lib.rs +++ b/turbopack/crates/turbopack-wasm/src/lib.rs @@ -9,6 +9,11 @@ #![feature(arbitrary_self_types)] #![feature(arbitrary_self_types_pointers)] +// Force linking `turbo-tasks-backend`'s `__tt_static_*` providers into +// this crate's test binary; see the matching dev-dep in `Cargo.toml`. +#[cfg(test)] +extern crate turbo_tasks_backend; + use anyhow::{Context, Result}; use turbo_rcstr::RcStr; use turbo_tasks::Vc;