Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/proc-macro-api/src/process.rs')
| -rw-r--r-- | crates/proc-macro-api/src/process.rs | 251 |
1 files changed, 153 insertions, 98 deletions
diff --git a/crates/proc-macro-api/src/process.rs b/crates/proc-macro-api/src/process.rs index f6a656e3ce..9f80880965 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; @@ -13,14 +17,13 @@ use span::Span; use stdx::JodChild; use crate::{ - Codec, ProcMacro, ProcMacroKind, ServerError, + ProcMacro, ProcMacroKind, ProtocolFormat, ServerError, bidirectional_protocol::{self, SubCallback, msg::BidirectionalMessage, reject_subrequests}, legacy_protocol::{self, SpanMode}, version, }; /// Represents a process handling proc-macro communication. -#[derive(Debug)] pub(crate) struct ProcMacroServerProcess { /// The state of the proc-macro server process, the protocol is currently strictly sequential /// hence the lock on the state. @@ -29,32 +32,103 @@ pub(crate) struct ProcMacroServerProcess { protocol: Protocol, /// Populated when the server exits. exited: OnceLock<AssertUnwindSafe<ServerError>>, + active: AtomicU32, +} + +impl std::fmt::Debug for ProcMacroServerProcess { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ProcMacroServerProcess") + .field("version", &self.version) + .field("protocol", &self.protocol) + .field("exited", &self.exited) + .finish() + } } #[derive(Debug, Clone)] pub(crate) enum Protocol { LegacyJson { mode: SpanMode }, - LegacyPostcard { mode: SpanMode }, BidirectionalPostcardPrototype { mode: SpanMode }, } +pub trait ProcessExit: Send + Sync { + fn exit_err(&mut self) -> Option<ServerError>; +} + +impl ProcessExit for Process { + fn exit_err(&mut self) -> Option<ServerError> { + match self.child.try_wait() { + Ok(None) | Err(_) => None, + Ok(Some(status)) => { + let mut msg = String::new(); + if !status.success() + && let Some(stderr) = self.child.stderr.as_mut() + { + _ = stderr.read_to_string(&mut msg); + } + Some(ServerError { + message: format!( + "proc-macro server exited with {status}{}{msg}", + if msg.is_empty() { "" } else { ": " } + ), + io: None, + }) + } + } + } +} + /// Maintains the state of the proc-macro server process. -#[derive(Debug)] -struct ProcessSrvState { - process: Process, - stdin: ChildStdin, - stdout: BufReader<ChildStdout>, +pub(crate) struct ProcessSrvState { + process: Box<dyn ProcessExit>, + stdin: Box<dyn Write + Send + Sync>, + stdout: Box<dyn BufRead + Send + Sync>, } impl ProcMacroServerProcess { /// Starts the proc-macro server and performs a version check - pub(crate) fn run<'a>( + pub(crate) fn spawn<'a>( process_path: &AbsPath, env: impl IntoIterator< Item = (impl AsRef<std::ffi::OsStr>, &'a Option<impl 'a + AsRef<std::ffi::OsStr>>), > + Clone, version: Option<&Version>, ) -> io::Result<ProcMacroServerProcess> { + Self::run( + |format| { + let mut process = Process::run( + process_path, + env.clone(), + format.map(|format| format.to_string()).as_deref(), + )?; + let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); + + Ok((Box::new(process), Box::new(stdin), Box::new(stdout))) + }, + version, + || { + #[expect(clippy::disallowed_methods)] + Command::new(process_path) + .arg("--version") + .output() + .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_owned()) + .unwrap_or_else(|_| "unknown version".to_owned()) + }, + ) + } + + /// Invokes `spawn` and performs a version check. + pub(crate) fn run( + spawn: impl Fn( + Option<ProtocolFormat>, + ) -> io::Result<( + Box<dyn ProcessExit>, + Box<dyn Write + Send + Sync>, + Box<dyn BufRead + Send + Sync>, + )>, + version: Option<&Version>, + binary_server_version: impl Fn() -> String, + ) -> io::Result<ProcMacroServerProcess> { const VERSION: Version = Version::new(1, 93, 0); // we do `>` for nightly as this started working in the middle of the 1.93 nightly release, so we dont want to break on half of the nightlies let has_working_format_flag = version.map_or(false, |v| { @@ -65,40 +139,38 @@ impl ProcMacroServerProcess { && has_working_format_flag { &[ - ( - Some("bidirectional-postcard-prototype"), - Protocol::BidirectionalPostcardPrototype { mode: SpanMode::Id }, - ), - (Some("postcard-legacy"), Protocol::LegacyPostcard { mode: SpanMode::Id }), - (Some("json-legacy"), Protocol::LegacyJson { mode: SpanMode::Id }), + Some(ProtocolFormat::BidirectionalPostcardPrototype), + Some(ProtocolFormat::JsonLegacy), ] } else { - &[(None, Protocol::LegacyJson { mode: SpanMode::Id })] + &[None] }; let mut err = None; - for &(format, ref protocol) in formats { + for &format in formats { let create_srv = || { - let mut process = Process::run(process_path, env.clone(), format)?; - let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); + let (process, stdin, stdout) = spawn(format)?; io::Result::Ok(ProcMacroServerProcess { state: Mutex::new(ProcessSrvState { process, stdin, stdout }), version: 0, - protocol: protocol.clone(), + protocol: match format { + Some(ProtocolFormat::BidirectionalPostcardPrototype) => { + Protocol::BidirectionalPostcardPrototype { mode: SpanMode::Id } + } + Some(ProtocolFormat::JsonLegacy) | None => { + Protocol::LegacyJson { mode: SpanMode::Id } + } + }, 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 => { - #[allow(clippy::disallowed_methods)] - let process_version = Command::new(process_path) - .arg("--version") - .output() - .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_owned()) - .unwrap_or_else(|_| "unknown version".to_owned()); + let process_version = binary_server_version(); err = Some(io::Error::other(format!( "Your installed proc-macro server is too new for your rust-analyzer. API version: {}, server version: {process_version}. \ This will prevent proc-macro expansion from working. Please consider updating your rust-analyzer to ensure compatibility with your current toolchain.", @@ -110,11 +182,10 @@ 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 } - | Protocol::LegacyPostcard { mode } | Protocol::BidirectionalPostcardPrototype { mode } => *mode = new_mode, } } @@ -132,15 +203,27 @@ 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<SubCallback<'_>>, + ) -> Result<Result<Vec<(String, ProcMacroKind)>, String>, ServerError> { + match self.protocol { + Protocol::LegacyJson { .. } => 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) } - pub(crate) fn use_postcard(&self) -> bool { - matches!(self.protocol, Protocol::LegacyPostcard { .. }) - } - /// Retrieves the API version of the proc-macro server. pub(crate) fn version(&self) -> u32 { self.version @@ -150,7 +233,6 @@ impl ProcMacroServerProcess { pub(crate) fn rust_analyzer_spans(&self) -> bool { match self.protocol { Protocol::LegacyJson { mode } => mode == SpanMode::RustAnalyzer, - Protocol::LegacyPostcard { mode } => mode == SpanMode::RustAnalyzer, Protocol::BidirectionalPostcardPrototype { mode } => mode == SpanMode::RustAnalyzer, } } @@ -158,9 +240,7 @@ impl ProcMacroServerProcess { /// Checks the API version of the running proc-macro server. fn version_check(&self, callback: Option<SubCallback<'_>>) -> Result<u32, ServerError> { match self.protocol { - Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { - legacy_protocol::version_check(self) - } + Protocol::LegacyJson { .. } => legacy_protocol::version_check(self), Protocol::BidirectionalPostcardPrototype { .. } => { let cb = callback.expect("callback required for bidirectional protocol"); bidirectional_protocol::version_check(self, cb) @@ -174,9 +254,7 @@ impl ProcMacroServerProcess { callback: Option<SubCallback<'_>>, ) -> Result<SpanMode, ServerError> { match self.protocol { - Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { - legacy_protocol::enable_rust_analyzer_spans(self) - } + Protocol::LegacyJson { .. } => legacy_protocol::enable_rust_analyzer_spans(self), Protocol::BidirectionalPostcardPrototype { .. } => { let cb = callback.expect("callback required for bidirectional protocol"); bidirectional_protocol::enable_rust_analyzer_spans(self, cb) @@ -184,23 +262,6 @@ impl ProcMacroServerProcess { } } - /// Finds proc-macros in a given dynamic library. - pub(crate) fn find_proc_macros( - &self, - dylib_path: &AbsPath, - callback: Option<SubCallback<'_>>, - ) -> Result<Result<Vec<(String, ProcMacroKind)>, 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, @@ -213,21 +274,22 @@ impl ProcMacroServerProcess { current_dir: String, callback: Option<SubCallback<'_>>, ) -> Result<Result<tt::TopSubtree, String>, ServerError> { - match self.protocol { - Protocol::LegacyJson { .. } | Protocol::LegacyPostcard { .. } => { - legacy_protocol::expand( - proc_macro, - subtree, - attr, - env, - def_site, - call_site, - mixed_site, - current_dir, - ) - } + self.active.fetch_add(1, Ordering::AcqRel); + let result = match self.protocol { + Protocol::LegacyJson { .. } => legacy_protocol::expand( + proc_macro, + self, + subtree, + attr, + env, + def_site, + call_site, + mixed_site, + current_dir, + ), Protocol::BidirectionalPostcardPrototype { .. } => bidirectional_protocol::expand( proc_macro, + self, subtree, attr, env, @@ -237,20 +299,23 @@ impl ProcMacroServerProcess { current_dir, callback.expect("callback required for bidirectional protocol"), ), - } + }; + + self.active.fetch_sub(1, Ordering::AcqRel); + result } - pub(crate) fn send_task<Request, Response, C: Codec>( + pub(crate) fn send_task_legacy<Request, Response>( &self, send: impl FnOnce( &mut dyn Write, &mut dyn BufRead, Request, - &mut C::Buf, + &mut String, ) -> Result<Option<Response>, ServerError>, req: Request, ) -> Result<Response, ServerError> { - self.with_locked_io::<C, _>(|writer, reader, buf| { + self.with_locked_io(String::new(), |writer, reader, buf| { send(writer, reader, req, buf).and_then(|res| { res.ok_or_else(|| { let message = "proc-macro server did not respond with data".to_owned(); @@ -266,31 +331,17 @@ impl ProcMacroServerProcess { }) } - pub(crate) fn with_locked_io<C: Codec, R>( + fn with_locked_io<R, B>( &self, - f: impl FnOnce(&mut dyn Write, &mut dyn BufRead, &mut C::Buf) -> Result<R, ServerError>, + mut buf: B, + f: impl FnOnce(&mut dyn Write, &mut dyn BufRead, &mut B) -> Result<R, ServerError>, ) -> Result<R, ServerError> { let state = &mut *self.state.lock().unwrap(); - let mut buf = C::Buf::default(); - f(&mut state.stdin, &mut state.stdout, &mut buf).map_err(|e| { if e.io.as_ref().map(|it| it.kind()) == Some(io::ErrorKind::BrokenPipe) { - match state.process.child.try_wait() { - Ok(None) | Err(_) => e, - Ok(Some(status)) => { - let mut msg = String::new(); - if !status.success() - && let Some(stderr) = state.process.child.stderr.as_mut() - { - _ = stderr.read_to_string(&mut msg); - } - let server_error = ServerError { - message: format!( - "proc-macro server exited with {status}{}{msg}", - if msg.is_empty() { "" } else { ": " } - ), - io: None, - }; + match state.process.exit_err() { + None => e, + Some(server_error) => { self.exited.get_or_init(|| AssertUnwindSafe(server_error)).0.clone() } } @@ -300,15 +351,19 @@ impl ProcMacroServerProcess { }) } - pub(crate) fn run_bidirectional<C: Codec>( + pub(crate) fn run_bidirectional( &self, initial: BidirectionalMessage, callback: SubCallback<'_>, ) -> Result<BidirectionalMessage, ServerError> { - self.with_locked_io::<C, _>(|writer, reader, buf| { - bidirectional_protocol::run_conversation::<C>(writer, reader, buf, initial, callback) + self.with_locked_io(Vec::new(), |writer, reader, buf| { + 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. |