diff --git a/crates/next-api/src/project.rs b/crates/next-api/src/project.rs index c55695da8af3e..e9ba90e91ef7f 100644 --- a/crates/next-api/src/project.rs +++ b/crates/next-api/src/project.rs @@ -900,6 +900,15 @@ impl Project { let module_graphs_op = whole_app_module_graph_operation(self); let module_graphs_vc = module_graphs_op.connect().resolve().await?; let _ = module_graphs_op.take_issues_with_path().await?; + + // At this point all modules have been computed and we can get rid of the node.js + // process pools + if self.await?.watch.enable { + turbopack_node::evaluate::scale_down(); + } else { + turbopack_node::evaluate::scale_zero(); + } + Ok(module_graphs_vc) } .instrument(tracing::info_span!("module graph for app")) diff --git a/turbopack/crates/turbopack-node/src/evaluate.rs b/turbopack/crates/turbopack-node/src/evaluate.rs index b7c7b23e0eada..5bd24e36b0025 100644 --- a/turbopack/crates/turbopack-node/src/evaluate.rs +++ b/turbopack/crates/turbopack-node/src/evaluate.rs @@ -652,6 +652,14 @@ impl EvaluateContext for BasicEvaluateContext { } } +pub fn scale_zero() { + NodeJsPool::scale_zero(); +} + +pub fn scale_down() { + NodeJsPool::scale_down(); +} + async fn print_error( error: StructuredError, pool: &NodeJsPool, diff --git a/turbopack/crates/turbopack-node/src/pool.rs b/turbopack/crates/turbopack-node/src/pool.rs index c07a1f9aa6a26..43b1883b867c6 100644 --- a/turbopack/crates/turbopack-node/src/pool.rs +++ b/turbopack/crates/turbopack-node/src/pool.rs @@ -13,6 +13,7 @@ use std::{ use anyhow::{bail, Context, Result}; use futures::join; +use once_cell::sync::Lazy; use owo_colors::{OwoColorize, Style}; use parking_lot::Mutex; use rustc_hash::FxHashMap; @@ -705,6 +706,9 @@ enum AcquiredPermits { }, } +static ACTIVE_POOLS: Lazy>>>>> = + Lazy::new(|| Default::default()); + /// A pool of Node.js workers operating on [entrypoint] with specific [cwd] and /// [env]. /// @@ -793,7 +797,14 @@ impl NodeJsPool { let idle_process_permit = idle_process_permit.context("acquiring idle process permit")?; let process = { let mut processes = self.processes.lock(); - processes.pop().unwrap() + let process = processes.pop().unwrap(); + { + let mut pools = ACTIVE_POOLS.lock(); + if let Some(idx) = pools.iter().position(|p| Arc::ptr_eq(p, &self.processes)) { + pools.swap_remove(idx); + } + } + process }; idle_process_permit.forget(); Ok((process, AcquiredPermits::Idle { concurrency_permit })) @@ -849,6 +860,26 @@ impl NodeJsPool { allow_process_reuse: true, }) } + + pub fn scale_down() { + let pools = ACTIVE_POOLS.lock().clone(); + for pool in pools { + let mut pool = pool.lock(); + let best = pool.pop().unwrap(); + pool.clear(); + pool.push(best); + pool.shrink_to_fit(); + } + } + + pub fn scale_zero() { + let pools = take(&mut *ACTIVE_POOLS.lock()); + for pool in pools { + let mut pool = pool.lock(); + pool.clear(); + pool.shrink_to_fit(); + } + } } pub struct NodeJsOperation { @@ -971,7 +1002,13 @@ impl Drop for NodeJsOperation { } if self.allow_process_reuse { process.cpu_time_invested += elapsed; - self.processes.lock().push(process); + { + let mut processes = self.processes.lock(); + if processes.is_empty() { + ACTIVE_POOLS.lock().push(self.processes.clone()); + } + processes.push(process); + } self.idle_process_semaphore.add_permits(1); } }