diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs index 33468a5003c3..8040a6f1888e 100644 --- a/crates/load-cargo/src/lib.rs +++ b/crates/load-cargo/src/lib.rs @@ -45,6 +45,7 @@ pub struct LoadCargoConfig { pub load_out_dirs_from_check: bool, pub with_proc_macro_server: ProcMacroServerChoice, pub prefill_caches: bool, + pub proc_macro_processes: usize, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -113,15 +114,25 @@ pub fn load_workspace_into_db( let proc_macro_server = match &load_config.with_proc_macro_server { ProcMacroServerChoice::Sysroot => ws.find_sysroot_proc_macro_srv().map(|it| { it.and_then(|it| { - ProcMacroClient::spawn(&it, extra_env, ws.toolchain.as_ref()).map_err(Into::into) + ProcMacroClient::spawn( + &it, + extra_env, + ws.toolchain.as_ref(), + load_config.proc_macro_processes, + ) + .map_err(Into::into) }) .map_err(|e| ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str())) }), - ProcMacroServerChoice::Explicit(path) => { - Some(ProcMacroClient::spawn(path, extra_env, ws.toolchain.as_ref()).map_err(|e| { - ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str()) - })) - } + ProcMacroServerChoice::Explicit(path) => Some( + ProcMacroClient::spawn( + path, + extra_env, + ws.toolchain.as_ref(), + load_config.proc_macro_processes, + ) + .map_err(|e| ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str())), + ), ProcMacroServerChoice::None => Some(Err(ProcMacroLoadingError::Disabled)), }; match &proc_macro_server { @@ -435,7 +446,7 @@ pub fn load_proc_macro( ) -> ProcMacroLoadResult { let res: Result, _> = (|| { let dylib = MacroDylib::new(path.to_path_buf()); - let vec = server.load_dylib(dylib, Some(&mut reject_subrequests)).map_err(|e| { + let vec = server.load_dylib(dylib, Some(&reject_subrequests)).map_err(|e| { ProcMacroLoadingError::ProcMacroSrvError(format!("{e}").into_boxed_str()) })?; if vec.is_empty() { @@ -541,7 +552,7 @@ impl ProcMacroExpander for Expander { mixed_site: Span, current_dir: String, ) -> Result { - let mut cb = |req| match req { + let cb = |req| match req { SubRequest::LocalFilePath { file_id } => { let file_id = FileId::from_raw(file_id); let source_root_id = db.file_source_root(file_id).source_root_id(db); @@ -600,7 +611,7 @@ impl ProcMacroExpander for Expander { call_site, mixed_site, current_dir, - Some(&mut cb), + Some(&cb), ) { Ok(Ok(subtree)) => Ok(subtree), Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err)), @@ -644,6 +655,7 @@ mod tests { load_out_dirs_from_check: false, with_proc_macro_server: ProcMacroServerChoice::None, prefill_caches: false, + proc_macro_processes: 1, }; let (db, _vfs, _proc_macro) = load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap(); diff --git a/crates/proc-macro-api/src/bidirectional_protocol.rs b/crates/proc-macro-api/src/bidirectional_protocol.rs index e44723a6a389..f23cc849d9ae 100644 --- a/crates/proc-macro-api/src/bidirectional_protocol.rs +++ b/crates/proc-macro-api/src/bidirectional_protocol.rs @@ -28,7 +28,7 @@ use crate::{ pub mod msg; -pub type SubCallback<'a> = &'a mut dyn FnMut(SubRequest) -> Result; +pub type SubCallback<'a> = &'a dyn Fn(SubRequest) -> Result; pub fn run_conversation( writer: &mut dyn Write, @@ -138,6 +138,7 @@ pub(crate) fn find_proc_macros( pub(crate) fn expand( proc_macro: &ProcMacro, + process: &ProcMacroServerProcess, subtree: tt::SubtreeView<'_>, attr: Option>, env: Vec<(String, String)>, @@ -147,7 +148,7 @@ pub(crate) fn expand( current_dir: String, callback: SubCallback<'_>, ) -> Result, crate::ServerError> { - let version = proc_macro.process.version(); + let version = process.version(); let mut span_data_table = SpanDataIndexMap::default(); let def_site = span_data_table.insert_full(def_site).0; let call_site = span_data_table.insert_full(call_site).0; @@ -164,7 +165,7 @@ pub(crate) fn expand( call_site, mixed_site, }, - span_data_table: if proc_macro.process.rust_analyzer_spans() { + span_data_table: if process.rust_analyzer_spans() { serialize_span_data_index_map(&span_data_table) } else { Vec::new() @@ -175,7 +176,7 @@ pub(crate) fn expand( current_dir: Some(current_dir), }))); - let response_payload = run_request(&proc_macro.process, task, callback)?; + let response_payload = run_request(process, task, callback)?; match response_payload { BidirectionalMessage::Response(Response::ExpandMacro(it)) => Ok(it diff --git a/crates/proc-macro-api/src/legacy_protocol.rs b/crates/proc-macro-api/src/legacy_protocol.rs index 4524d1b66bfe..ab9a0316a21d 100644 --- a/crates/proc-macro-api/src/legacy_protocol.rs +++ b/crates/proc-macro-api/src/legacy_protocol.rs @@ -18,8 +18,7 @@ use crate::{ flat::serialize_span_data_index_map, }, process::ProcMacroServerProcess, - transport::codec::Codec, - transport::codec::{json::JsonProtocol, postcard::PostcardProtocol}, + transport::codec::{Codec, json::JsonProtocol, postcard::PostcardProtocol}, version, }; @@ -77,6 +76,7 @@ pub(crate) fn find_proc_macros( pub(crate) fn expand( proc_macro: &ProcMacro, + process: &ProcMacroServerProcess, subtree: tt::SubtreeView<'_>, attr: Option>, env: Vec<(String, String)>, @@ -85,7 +85,7 @@ pub(crate) fn expand( mixed_site: Span, current_dir: String, ) -> Result, crate::ServerError> { - let version = proc_macro.process.version(); + let version = process.version(); let mut span_data_table = SpanDataIndexMap::default(); let def_site = span_data_table.insert_full(def_site).0; let call_site = span_data_table.insert_full(call_site).0; @@ -102,7 +102,7 @@ pub(crate) fn expand( call_site, mixed_site, }, - span_data_table: if proc_macro.process.rust_analyzer_spans() { + span_data_table: if process.rust_analyzer_spans() { serialize_span_data_index_map(&span_data_table) } else { Vec::new() @@ -113,7 +113,7 @@ pub(crate) fn expand( current_dir: Some(current_dir), }; - let response = send_task(&proc_macro.process, Request::ExpandMacro(Box::new(task)))?; + let response = send_task(process, Request::ExpandMacro(Box::new(task)))?; match response { Response::ExpandMacro(it) => Ok(it diff --git a/crates/proc-macro-api/src/lib.rs b/crates/proc-macro-api/src/lib.rs index 98ee6817c2d2..016f27761c81 100644 --- a/crates/proc-macro-api/src/lib.rs +++ b/crates/proc-macro-api/src/lib.rs @@ -18,6 +18,7 @@ extern crate rustc_driver as _; pub mod bidirectional_protocol; pub mod legacy_protocol; +pub mod pool; pub mod process; pub mod transport; @@ -27,7 +28,9 @@ use span::{ErasedFileAstId, FIXUP_ERASED_FILE_AST_ID_MARKER, Span}; use std::{fmt, io, sync::Arc, time::SystemTime}; pub use crate::transport::codec::Codec; -use crate::{bidirectional_protocol::SubCallback, process::ProcMacroServerProcess}; +use crate::{ + bidirectional_protocol::SubCallback, pool::ProcMacroServerPool, process::ProcMacroServerProcess, +}; /// The versions of the server protocol pub mod version { @@ -84,7 +87,7 @@ pub struct ProcMacroClient { /// /// That means that concurrent salsa requests may block each other when expanding proc macros, /// which is unfortunate, but simple and good enough for the time being. - process: Arc, + pool: Arc, path: AbsPathBuf, } @@ -106,7 +109,7 @@ impl MacroDylib { /// we share a single expander process for all macros within a workspace. #[derive(Debug, Clone)] pub struct ProcMacro { - process: Arc, + pool: ProcMacroServerPool, dylib_path: Arc, name: Box, kind: ProcMacroKind, @@ -120,7 +123,6 @@ impl PartialEq for ProcMacro { && self.kind == other.kind && self.dylib_path == other.dylib_path && self.dylib_last_modified == other.dylib_last_modified - && Arc::ptr_eq(&self.process, &other.process) } } @@ -150,9 +152,17 @@ impl ProcMacroClient { Item = (impl AsRef, &'a Option>), > + Clone, version: Option<&Version>, + num_process: usize, ) -> io::Result { - let process = ProcMacroServerProcess::spawn(process_path, env, version)?; - Ok(ProcMacroClient { process: Arc::new(process), path: process_path.to_owned() }) + let pool_size = num_process; + let mut workers = Vec::with_capacity(pool_size); + for _ in 0..pool_size { + let worker = ProcMacroServerProcess::spawn(process_path, env.clone(), version)?; + workers.push(worker); + } + + let pool = ProcMacroServerPool::new(workers); + Ok(ProcMacroClient { pool: Arc::new(pool), path: process_path.to_owned() }) } /// Invokes `spawn` and returns a client connected to the resulting read and write handles. @@ -166,11 +176,20 @@ impl ProcMacroClient { Box, Box, Box, - )>, + )> + Clone, version: Option<&Version>, + num_process: usize, ) -> io::Result { - let process = ProcMacroServerProcess::run(spawn, version, || "".to_owned())?; - Ok(ProcMacroClient { process: Arc::new(process), path: process_path.to_owned() }) + let pool_size = num_process; + let mut workers = Vec::with_capacity(pool_size); + for _ in 0..pool_size { + let worker = + ProcMacroServerProcess::run(spawn.clone(), version, || "".to_owned())?; + workers.push(worker); + } + + let pool = ProcMacroServerPool::new(workers); + Ok(ProcMacroClient { pool: Arc::new(pool), path: process_path.to_owned() }) } /// Returns the absolute path to the proc-macro server. @@ -184,31 +203,12 @@ impl ProcMacroClient { dylib: MacroDylib, callback: Option>, ) -> Result, ServerError> { - let _p = tracing::info_span!("ProcMacroServer::load_dylib").entered(); - let macros = self.process.find_proc_macros(&dylib.path, callback)?; - - let dylib_path = Arc::new(dylib.path); - let dylib_last_modified = std::fs::metadata(dylib_path.as_path()) - .ok() - .and_then(|metadata| metadata.modified().ok()); - match macros { - Ok(macros) => Ok(macros - .into_iter() - .map(|(name, kind)| ProcMacro { - process: self.process.clone(), - name: name.into(), - kind, - dylib_path: dylib_path.clone(), - dylib_last_modified, - }) - .collect()), - Err(message) => Err(ServerError { message, io: None }), - } + self.pool.load_dylib(&dylib, callback) } /// Checks if the proc-macro server has exited. pub fn exited(&self) -> Option<&ServerError> { - self.process.exited() + self.pool.exited() } } @@ -224,7 +224,7 @@ impl ProcMacro { } fn needs_fixup_change(&self) -> bool { - let version = self.process.version(); + let version = self.pool.version(); (version::RUST_ANALYZER_SPAN_SUPPORT..version::HASHED_AST_ID).contains(&version) } @@ -268,7 +268,7 @@ impl ProcMacro { } } - self.process.expand( + self.pool.pick_process()?.expand( self, subtree, attr, diff --git a/crates/proc-macro-api/src/pool.rs b/crates/proc-macro-api/src/pool.rs new file mode 100644 index 000000000000..a637bc0e480a --- /dev/null +++ b/crates/proc-macro-api/src/pool.rs @@ -0,0 +1,91 @@ +//! A pool of proc-macro server processes +use std::sync::Arc; + +use crate::{ + MacroDylib, ProcMacro, ServerError, bidirectional_protocol::SubCallback, + process::ProcMacroServerProcess, +}; + +#[derive(Debug, Clone)] +pub(crate) struct ProcMacroServerPool { + workers: Arc<[ProcMacroServerProcess]>, + version: u32, +} + +impl ProcMacroServerPool { + pub(crate) fn new(workers: Vec) -> Self { + let version = workers[0].version(); + Self { workers: workers.into(), version } + } +} + +impl ProcMacroServerPool { + pub(crate) fn exited(&self) -> Option<&ServerError> { + for worker in &*self.workers { + worker.exited()?; + } + self.workers[0].exited() + } + + pub(crate) fn pick_process(&self) -> Result<&ProcMacroServerProcess, ServerError> { + let mut best: Option<&ProcMacroServerProcess> = None; + let mut best_load = u32::MAX; + + for w in self.workers.iter().filter(|w| w.exited().is_none()) { + let load = w.number_of_active_req(); + + if load == 0 { + return Ok(w); + } + + if load < best_load { + best = Some(w); + best_load = load; + } + } + + best.ok_or_else(|| ServerError { + message: "all proc-macro server workers have exited".into(), + io: None, + }) + } + + pub(crate) fn load_dylib( + &self, + dylib: &MacroDylib, + callback: Option>, + ) -> Result, ServerError> { + let _span = tracing::info_span!("ProcMacroServer::load_dylib").entered(); + + let dylib_path = Arc::new(dylib.path.clone()); + let dylib_last_modified = + std::fs::metadata(dylib_path.as_path()).ok().and_then(|m| m.modified().ok()); + + let (first, rest) = self.workers.split_first().expect("worker pool must not be empty"); + + let macros = first + .find_proc_macros(&dylib.path, callback)? + .map_err(|e| ServerError { message: e, io: None })?; + + for worker in rest { + worker + .find_proc_macros(&dylib.path, callback)? + .map_err(|e| ServerError { message: e, io: None })?; + } + + Ok(macros + .into_iter() + .map(|(name, kind)| ProcMacro { + pool: self.clone(), + name: name.into(), + kind, + dylib_path: dylib_path.clone(), + dylib_last_modified, + }) + .collect()) + } + + pub(crate) fn version(&self) -> u32 { + self.version + } +} diff --git a/crates/proc-macro-api/src/process.rs b/crates/proc-macro-api/src/process.rs index 4f8762158790..33caa9ada5bf 100644 --- a/crates/proc-macro-api/src/process.rs +++ b/crates/proc-macro-api/src/process.rs @@ -1,10 +1,14 @@ //! Handle process life-time and message passing for proc-macro client use std::{ + fmt::Debug, io::{self, BufRead, BufReader, Read, Write}, panic::AssertUnwindSafe, process::{Child, ChildStdin, ChildStdout, Command, Stdio}, - sync::{Arc, Mutex, OnceLock}, + sync::{ + Arc, Mutex, OnceLock, + atomic::{AtomicU32, Ordering}, + }, }; use paths::AbsPath; @@ -28,6 +32,7 @@ pub(crate) struct ProcMacroServerProcess { protocol: Protocol, /// Populated when the server exits. exited: OnceLock>, + active: AtomicU32, } impl std::fmt::Debug for ProcMacroServerProcess { @@ -75,7 +80,7 @@ impl ProcessExit for Process { } /// Maintains the state of the proc-macro server process. -struct ProcessSrvState { +pub(crate) struct ProcessSrvState { process: Box, stdin: Box, stdout: Box, @@ -163,11 +168,12 @@ impl ProcMacroServerProcess { } }, exited: OnceLock::new(), + active: AtomicU32::new(0), }) }; let mut srv = create_srv()?; tracing::info!("sending proc-macro server version check"); - match srv.version_check(Some(&mut reject_subrequests)) { + match srv.version_check(Some(&reject_subrequests)) { Ok(v) if v > version::CURRENT_API_VERSION => { let process_version = binary_server_version(); err = Some(io::Error::other(format!( @@ -181,7 +187,7 @@ impl ProcMacroServerProcess { srv.version = v; if srv.version >= version::RUST_ANALYZER_SPAN_SUPPORT && let Ok(new_mode) = - srv.enable_rust_analyzer_spans(Some(&mut reject_subrequests)) + srv.enable_rust_analyzer_spans(Some(&reject_subrequests)) { match &mut srv.protocol { Protocol::LegacyJson { mode } @@ -203,6 +209,23 @@ impl ProcMacroServerProcess { Err(err.unwrap()) } + /// Finds proc-macros in a given dynamic library. + pub(crate) fn find_proc_macros( + &self, + dylib_path: &AbsPath, + callback: Option>, + ) -> Result, String>, ServerError> { + match self.protocol { + Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { + legacy_protocol::find_proc_macros(self, dylib_path) + } + Protocol::BidirectionalPostcardPrototype { .. } => { + let cb = callback.expect("callback required for bidirectional protocol"); + bidirectional_protocol::find_proc_macros(self, dylib_path, cb) + } + } + } + /// Returns the server error if the process has exited. pub(crate) fn exited(&self) -> Option<&ServerError> { self.exited.get().map(|it| &it.0) @@ -255,23 +278,6 @@ impl ProcMacroServerProcess { } } - /// Finds proc-macros in a given dynamic library. - pub(crate) fn find_proc_macros( - &self, - dylib_path: &AbsPath, - callback: Option>, - ) -> Result, String>, ServerError> { - match self.protocol { - Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { - legacy_protocol::find_proc_macros(self, dylib_path) - } - Protocol::BidirectionalPostcardPrototype { .. } => { - let cb = callback.expect("callback required for bidirectional protocol"); - bidirectional_protocol::find_proc_macros(self, dylib_path, cb) - } - } - } - pub(crate) fn expand( &self, proc_macro: &ProcMacro, @@ -284,10 +290,12 @@ impl ProcMacroServerProcess { current_dir: String, callback: Option>, ) -> Result, ServerError> { - match self.protocol { + self.active.fetch_add(1, Ordering::AcqRel); + let result = match self.protocol { Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { legacy_protocol::expand( proc_macro, + self, subtree, attr, env, @@ -299,6 +307,7 @@ impl ProcMacroServerProcess { } Protocol::BidirectionalPostcardPrototype { .. } => bidirectional_protocol::expand( proc_macro, + self, subtree, attr, env, @@ -308,7 +317,10 @@ impl ProcMacroServerProcess { current_dir, callback.expect("callback required for bidirectional protocol"), ), - } + }; + + self.active.fetch_sub(1, Ordering::AcqRel); + result } pub(crate) fn send_task( @@ -367,6 +379,10 @@ impl ProcMacroServerProcess { bidirectional_protocol::run_conversation::(writer, reader, buf, initial, callback) }) } + + pub(crate) fn number_of_active_req(&self) -> u32 { + self.active.load(Ordering::Acquire) + } } /// Manages the execution of the proc-macro server process. diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index a02d1a78564f..1995d3889891 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -91,6 +91,7 @@ impl flags::AnalysisStats { } }, prefill_caches: false, + proc_macro_processes: 1, }; let build_scripts_time = if self.disable_build_scripts { diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs index 776069f155f0..575c77f8428c 100644 --- a/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/crates/rust-analyzer/src/cli/diagnostics.rs @@ -41,6 +41,7 @@ impl flags::Diagnostics { load_out_dirs_from_check: !self.disable_build_scripts, with_proc_macro_server, prefill_caches: false, + proc_macro_processes: 1, }; let (db, _vfs, _proc_macro) = load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?; diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs index f3b0699d5515..e5e238db6361 100644 --- a/crates/rust-analyzer/src/cli/lsif.rs +++ b/crates/rust-analyzer/src/cli/lsif.rs @@ -293,6 +293,7 @@ impl flags::Lsif { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let path = AbsPathBuf::assert_utf8(env::current_dir()?.join(self.path)); let root = ProjectManifest::discover_single(&path)?; diff --git a/crates/rust-analyzer/src/cli/prime_caches.rs b/crates/rust-analyzer/src/cli/prime_caches.rs index 467d8a53884a..d5da6791797b 100644 --- a/crates/rust-analyzer/src/cli/prime_caches.rs +++ b/crates/rust-analyzer/src/cli/prime_caches.rs @@ -38,6 +38,7 @@ impl flags::PrimeCaches { // we want to ensure that this command, not `load_workspace_at`, // is responsible for that work. prefill_caches: false, + proc_macro_processes: config.proc_macro_num_processes(), }; let root = AbsPathBuf::assert_utf8(std::env::current_dir()?.join(root)); diff --git a/crates/rust-analyzer/src/cli/run_tests.rs b/crates/rust-analyzer/src/cli/run_tests.rs index 82ace8c8b315..d4a56d773e7d 100644 --- a/crates/rust-analyzer/src/cli/run_tests.rs +++ b/crates/rust-analyzer/src/cli/run_tests.rs @@ -23,6 +23,7 @@ impl flags::RunTests { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let (ref db, _vfs, _proc_macro) = load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?; diff --git a/crates/rust-analyzer/src/cli/rustc_tests.rs b/crates/rust-analyzer/src/cli/rustc_tests.rs index 249566d2ac16..e8c6c5f4d4f7 100644 --- a/crates/rust-analyzer/src/cli/rustc_tests.rs +++ b/crates/rust-analyzer/src/cli/rustc_tests.rs @@ -103,6 +103,7 @@ impl Tester { load_out_dirs_from_check: false, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let (db, _vfs, _proc_macro) = load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?; diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs index 271d2507bcfe..ed0476697c9c 100644 --- a/crates/rust-analyzer/src/cli/scip.rs +++ b/crates/rust-analyzer/src/cli/scip.rs @@ -52,6 +52,7 @@ impl flags::Scip { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, + proc_macro_processes: config.proc_macro_num_processes(), }; let cargo_config = config.cargo(None); let (db, vfs, _) = load_workspace_at( diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs index 39186831459c..5c69bda723fb 100644 --- a/crates/rust-analyzer/src/cli/ssr.rs +++ b/crates/rust-analyzer/src/cli/ssr.rs @@ -20,6 +20,7 @@ impl flags::Ssr { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let (ref db, vfs, _proc_macro) = load_workspace_at( &std::env::current_dir()?, @@ -56,6 +57,7 @@ impl flags::Search { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let (ref db, _vfs, _proc_macro) = load_workspace_at( &std::env::current_dir()?, diff --git a/crates/rust-analyzer/src/cli/unresolved_references.rs b/crates/rust-analyzer/src/cli/unresolved_references.rs index 294add682d01..49c6fcb91ebf 100644 --- a/crates/rust-analyzer/src/cli/unresolved_references.rs +++ b/crates/rust-analyzer/src/cli/unresolved_references.rs @@ -44,6 +44,7 @@ impl flags::UnresolvedReferences { load_out_dirs_from_check: !self.disable_build_scripts, with_proc_macro_server, prefill_caches: false, + proc_macro_processes: config.proc_macro_num_processes(), }; let (db, vfs, _proc_macro) = load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?; diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 28ac94e4deb6..98495f6150da 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -387,6 +387,12 @@ config_data! { /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`. procMacro_enable: bool = true, + /// Number of proc-macro server processes to spawn. + /// + /// Controls how many independent `proc-macro-srv` processes rust-analyzer + /// runs in parallel to handle macro expansion. + procMacro_processes: NumProcesses = NumProcesses::Concrete(1), + /// Internal config, path to proc-macro server executable. procMacro_server: Option = None, @@ -2641,6 +2647,13 @@ impl Config { } } + pub fn proc_macro_num_processes(&self) -> usize { + match self.procMacro_processes() { + NumProcesses::Concrete(0) | NumProcesses::Physical => num_cpus::get_physical(), + &NumProcesses::Concrete(n) => n, + } + } + pub fn main_loop_num_threads(&self) -> usize { match self.numThreads() { Some(NumThreads::Concrete(0)) | None | Some(NumThreads::Physical) => { @@ -3077,6 +3090,14 @@ pub enum NumThreads { Concrete(usize), } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum NumProcesses { + Physical, + #[serde(untagged)] + Concrete(usize), +} + macro_rules! _default_val { ($default:expr, $ty:ty) => {{ let default_: $ty = $default; @@ -3903,6 +3924,22 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json }, ], }, + "NumProcesses" => set! { + "anyOf": [ + { + "type": "number", + "minimum": 0, + "maximum": 255 + }, + { + "type": "string", + "enum": ["physical"], + "enumDescriptions": [ + "Use the number of physical cores", + ], + }, + ], + }, "Option" => set! { "anyOf": [ { diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index c61825b99fec..d16ca2fb48ac 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -53,6 +53,7 @@ fn integrated_highlighting_benchmark() { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, + proc_macro_processes: 1, }; let (db, vfs, _proc_macro) = { @@ -121,6 +122,7 @@ fn integrated_completion_benchmark() { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, + proc_macro_processes: 1, }; let (db, vfs, _proc_macro) = { @@ -322,6 +324,7 @@ fn integrated_diagnostics_benchmark() { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, + proc_macro_processes: 1, }; let (db, vfs, _proc_macro) = { diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index ccafbd7b30b9..83f4a19b39fa 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -701,15 +701,19 @@ impl GlobalState { _ => Default::default(), }; info!("Using proc-macro server at {path}"); + let num_process = self.config.proc_macro_num_processes(); - Some(ProcMacroClient::spawn(&path, &env, ws.toolchain.as_ref()).map_err(|err| { - tracing::error!( - "Failed to run proc-macro server from path {path}, error: {err:?}", - ); - anyhow::format_err!( - "Failed to run proc-macro server from path {path}, error: {err:?}", - ) - })) + Some( + ProcMacroClient::spawn(&path, &env, ws.toolchain.as_ref(), num_process) + .map_err(|err| { + tracing::error!( + "Failed to run proc-macro server from path {path}, error: {err:?}", + ); + anyhow::format_err!( + "Failed to run proc-macro server from path {path}, error: {err:?}", + ) + }), + ) })) } diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md index c4124aaae075..5b1a2e111196 100644 --- a/docs/book/src/configuration_generated.md +++ b/docs/book/src/configuration_generated.md @@ -1318,6 +1318,16 @@ These proc-macros will be ignored when trying to expand them. This config takes a map of crate names with the exported proc-macro names to ignore as values. +## rust-analyzer.procMacro.processes {#procMacro.processes} + +Default: `1` + +Number of proc-macro server processes to spawn. + +Controls how many independent `proc-macro-srv` processes rust-analyzer +runs in parallel to handle macro expansion. + + ## rust-analyzer.procMacro.server {#procMacro.server} Default: `null` diff --git a/editors/code/package.json b/editors/code/package.json index 0d91378706a4..406e41767f6d 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -2770,6 +2770,31 @@ } } }, + { + "title": "Proc Macro", + "properties": { + "rust-analyzer.procMacro.processes": { + "markdownDescription": "Number of proc-macro server processes to spawn.\n\nControls how many independent `proc-macro-srv` processes rust-analyzer\nruns in parallel to handle macro expansion.", + "default": 1, + "anyOf": [ + { + "type": "number", + "minimum": 0, + "maximum": 255 + }, + { + "type": "string", + "enum": [ + "physical" + ], + "enumDescriptions": [ + "Use the number of physical cores" + ] + } + ] + } + } + }, { "title": "Proc Macro", "properties": {