Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/flycheck/src/lib.rs')
| -rw-r--r-- | crates/flycheck/src/lib.rs | 105 |
1 files changed, 75 insertions, 30 deletions
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index 2cd1995267..df666ae097 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -2,12 +2,16 @@ //! another compatible command (f.x. clippy) in a background thread and provide //! LSP diagnostics based on the output of the command. -use std::{fmt, io, process::Command, time::Duration}; +use std::{ + fmt, io, + process::{ChildStderr, ChildStdout, Command, Stdio}, + time::Duration, +}; use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; use paths::AbsPathBuf; use serde::Deserialize; -use stdx::process::streaming_output; +use stdx::{process::streaming_output, JodChild}; pub use cargo_metadata::diagnostic::{ Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan, @@ -117,7 +121,7 @@ struct FlycheckActor { sender: Box<dyn Fn(Message) + Send>, config: FlycheckConfig, workspace_root: AbsPathBuf, - /// WatchThread exists to wrap around the communication needed to be able to + /// CargoHandle exists to wrap around the communication needed to be able to /// run `cargo check` without blocking. Currently the Rust standard library /// doesn't provide a way to read sub-process output without blocking, so we /// have to wrap sub-processes output handling in a thread and pass messages @@ -153,18 +157,36 @@ impl FlycheckActor { while let Some(event) = self.next_event(&inbox) { match event { Event::Restart(Restart) => { + if let Some(cargo_handle) = self.cargo_handle.take() { + // Cancel the previously spawned process + cargo_handle.cancel(); + } while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {} - - self.cancel_check_process(); + self.progress(Progress::DidCancel); let command = self.check_command(); - tracing::info!("restart flycheck {:?}", command); - self.cargo_handle = Some(CargoHandle::spawn(command)); - self.progress(Progress::DidStart); + tracing::debug!(?command, "will restart flycheck"); + match CargoHandle::spawn(command) { + Ok(cargo_handle) => { + tracing::debug!( + command = ?self.check_command(), + "did restart flycheck" + ); + self.cargo_handle = Some(cargo_handle); + self.progress(Progress::DidStart); + } + Err(error) => { + tracing::error!( + command = ?self.check_command(), + %error, "failed to restart flycheck" + ); + } + } } Event::CheckEvent(None) => { - // Watcher finished, replace it with a never channel to - // avoid busy-waiting. + tracing::debug!("flycheck finished"); + + // Watcher finished let cargo_handle = self.cargo_handle.take().unwrap(); let res = cargo_handle.join(); if res.is_err() { @@ -192,8 +214,10 @@ impl FlycheckActor { // If we rerun the thread, we need to discard the previous check results first self.cancel_check_process(); } + fn cancel_check_process(&mut self) { - if self.cargo_handle.take().is_some() { + if let Some(cargo_handle) = self.cargo_handle.take() { + cargo_handle.cancel(); self.progress(Progress::DidCancel); } } @@ -249,37 +273,64 @@ impl FlycheckActor { } } +/// A handle to a cargo process used for fly-checking. struct CargoHandle { - thread: jod_thread::JoinHandle<io::Result<()>>, + /// The handle to the actual cargo process. As we cannot cancel directly from with + /// a read syscall dropping and therefor terminating the process is our best option. + child: JodChild, + thread: jod_thread::JoinHandle<io::Result<(bool, String)>>, receiver: Receiver<CargoMessage>, } impl CargoHandle { - fn spawn(command: Command) -> CargoHandle { + fn spawn(mut command: Command) -> std::io::Result<CargoHandle> { + command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); + let mut child = JodChild::spawn(command)?; + + let stdout = child.stdout.take().unwrap(); + let stderr = child.stderr.take().unwrap(); + let (sender, receiver) = unbounded(); - let actor = CargoActor::new(sender); + let actor = CargoActor::new(sender, stdout, stderr); let thread = jod_thread::Builder::new() .name("CargoHandle".to_owned()) - .spawn(move || actor.run(command)) + .spawn(move || actor.run()) .expect("failed to spawn thread"); - CargoHandle { thread, receiver } + Ok(CargoHandle { child, thread, receiver }) } - fn join(self) -> io::Result<()> { - self.thread.join() + fn cancel(mut self) { + let _ = self.child.kill(); + let _ = self.child.wait(); + } + + fn join(mut self) -> io::Result<()> { + let _ = self.child.kill(); + let exit_status = self.child.wait()?; + let (read_at_least_one_message, error) = self.thread.join()?; + if read_at_least_one_message || exit_status.success() { + Ok(()) + } else { + Err(io::Error::new(io::ErrorKind::Other, format!( + "Cargo watcher failed, the command produced no valid metadata (exit code: {:?}):\n{}", + exit_status, error + ))) + } } } struct CargoActor { sender: Sender<CargoMessage>, + stdout: ChildStdout, + stderr: ChildStderr, } impl CargoActor { - fn new(sender: Sender<CargoMessage>) -> CargoActor { - CargoActor { sender } + fn new(sender: Sender<CargoMessage>, stdout: ChildStdout, stderr: ChildStderr) -> CargoActor { + CargoActor { sender, stdout, stderr } } - fn run(self, command: Command) -> io::Result<()> { + fn run(self) -> io::Result<(bool, String)> { // We manually read a line at a time, instead of using serde's // stream deserializers, because the deserializer cannot recover // from an error, resulting in it getting stuck, because we try to @@ -292,7 +343,8 @@ impl CargoActor { let mut error = String::new(); let mut read_at_least_one_message = false; let output = streaming_output( - command, + self.stdout, + self.stderr, &mut |line| { read_at_least_one_message = true; @@ -325,14 +377,7 @@ impl CargoActor { }, ); match output { - Ok(_) if read_at_least_one_message => Ok(()), - Ok(output) if output.status.success() => Ok(()), - Ok(output) => { - Err(io::Error::new(io::ErrorKind::Other, format!( - "Cargo watcher failed, the command produced no valid metadata (exit code: {:?}):\n{}", - output.status, error - ))) - } + Ok(_) => Ok((read_at_least_one_message, error)), Err(e) => Err(io::Error::new(e.kind(), format!("{:?}: {}", e, error))), } } |