22//! another compatible command (f.x. clippy) in a background thread and provide
33//! LSP diagnostics based on the output of the command.
44
5- use std:: { fmt, io, process:: Command , time:: Duration } ;
5+ use std:: {
6+ fmt, io,
7+ process:: { ChildStderr , ChildStdout , Command , Stdio } ,
8+ time:: Duration ,
9+ } ;
610
711use crossbeam_channel:: { never, select, unbounded, Receiver , Sender } ;
812use paths:: AbsPathBuf ;
913use serde:: Deserialize ;
10- use stdx:: process:: streaming_output;
14+ use stdx:: { process:: streaming_output, JodChild } ;
1115
1216pub use cargo_metadata:: diagnostic:: {
1317 Applicability , Diagnostic , DiagnosticCode , DiagnosticLevel , DiagnosticSpan ,
@@ -117,7 +121,7 @@ struct FlycheckActor {
117121 sender : Box < dyn Fn ( Message ) + Send > ,
118122 config : FlycheckConfig ,
119123 workspace_root : AbsPathBuf ,
120- /// WatchThread exists to wrap around the communication needed to be able to
124+ /// CargoHandle exists to wrap around the communication needed to be able to
121125 /// run `cargo check` without blocking. Currently the Rust standard library
122126 /// doesn't provide a way to read sub-process output without blocking, so we
123127 /// have to wrap sub-processes output handling in a thread and pass messages
@@ -153,14 +157,24 @@ impl FlycheckActor {
153157 while let Some ( event) = self . next_event ( & inbox) {
154158 match event {
155159 Event :: Restart ( Restart ) => {
160+ // Drop and cancel the previously spawned process
161+ self . cargo_handle . take ( ) ;
156162 while let Ok ( Restart ) = inbox. recv_timeout ( Duration :: from_millis ( 50 ) ) { }
157163
158164 self . cancel_check_process ( ) ;
159165
160166 let command = self . check_command ( ) ;
161- tracing:: info!( "restart flycheck {:?}" , command) ;
162- self . cargo_handle = Some ( CargoHandle :: spawn ( command) ) ;
163- self . progress ( Progress :: DidStart ) ;
167+ let command_f = format ! ( "restart flycheck {command:?}" ) ;
168+ match CargoHandle :: spawn ( command) {
169+ Ok ( cargo_handle) => {
170+ tracing:: info!( "{}" , command_f) ;
171+ self . cargo_handle = Some ( cargo_handle) ;
172+ self . progress ( Progress :: DidStart ) ;
173+ }
174+ Err ( e) => {
175+ tracing:: error!( "{command_f} failed: {e:?}" , ) ;
176+ }
177+ }
164178 }
165179 Event :: CheckEvent ( None ) => {
166180 // Watcher finished, replace it with a never channel to
@@ -249,37 +263,58 @@ impl FlycheckActor {
249263 }
250264}
251265
266+ /// A handle to a cargo process used for fly-checking.
252267struct CargoHandle {
253- thread : jod_thread:: JoinHandle < io:: Result < ( ) > > ,
268+ /// The handle to the actual cargo process. As we cannot cancel directly from with
269+ /// a read syscall dropping and therefor terminating the process is our best option.
270+ child : JodChild ,
271+ thread : jod_thread:: JoinHandle < io:: Result < ( bool , String ) > > ,
254272 receiver : Receiver < CargoMessage > ,
255273}
256274
257275impl CargoHandle {
258- fn spawn ( command : Command ) -> CargoHandle {
276+ fn spawn ( mut command : Command ) -> std:: io:: Result < CargoHandle > {
277+ command. stdout ( Stdio :: piped ( ) ) . stderr ( Stdio :: piped ( ) ) . stdin ( Stdio :: null ( ) ) ;
278+ let mut child = JodChild :: spawn ( command) ?;
279+
280+ let stdout = child. stdout . take ( ) . unwrap ( ) ;
281+ let stderr = child. stderr . take ( ) . unwrap ( ) ;
282+
259283 let ( sender, receiver) = unbounded ( ) ;
260- let actor = CargoActor :: new ( sender) ;
284+ let actor = CargoActor :: new ( sender, stdout , stderr ) ;
261285 let thread = jod_thread:: Builder :: new ( )
262286 . name ( "CargoHandle" . to_owned ( ) )
263- . spawn ( move || actor. run ( command ) )
287+ . spawn ( move || actor. run ( ) )
264288 . expect ( "failed to spawn thread" ) ;
265- CargoHandle { thread, receiver }
289+ Ok ( CargoHandle { child , thread, receiver } )
266290 }
267291
268292 fn join ( self ) -> io:: Result < ( ) > {
269- self . thread . join ( )
293+ let exit_status = self . child . wait ( ) ?;
294+ let ( read_at_least_one_message, error) = self . thread . join ( ) ?;
295+ if read_at_least_one_message || exit_status. success ( ) {
296+ Ok ( ( ) )
297+ } else {
298+ Err ( io:: Error :: new ( io:: ErrorKind :: Other , format ! (
299+ "Cargo watcher failed, the command produced no valid metadata (exit code: {:?}):\n {}" ,
300+ exit_status, error
301+ ) ) )
302+ }
270303 }
271304}
272305
273306struct CargoActor {
274307 sender : Sender < CargoMessage > ,
308+ stdout : ChildStdout ,
309+ stderr : ChildStderr ,
275310}
276311
277312impl CargoActor {
278- fn new ( sender : Sender < CargoMessage > ) -> CargoActor {
279- CargoActor { sender }
313+ fn new ( sender : Sender < CargoMessage > , stdout : ChildStdout , stderr : ChildStderr ) -> CargoActor {
314+ CargoActor { sender, stdout , stderr }
280315 }
281316
282- fn run ( self , command : Command ) -> io:: Result < ( ) > {
317+ fn run ( self ) -> io:: Result < ( bool , String ) > {
283318 // We manually read a line at a time, instead of using serde's
284319 // stream deserializers, because the deserializer cannot recover
285320 // from an error, resulting in it getting stuck, because we try to
@@ -292,7 +327,8 @@ impl CargoActor {
292327 let mut error = String :: new ( ) ;
293328 let mut read_at_least_one_message = false ;
294329 let output = streaming_output (
295- command,
330+ self . stdout ,
331+ self . stderr ,
296332 & mut |line| {
297333 read_at_least_one_message = true ;
298334
@@ -325,14 +361,7 @@ impl CargoActor {
325361 } ,
326362 ) ;
327363 match output {
328- Ok ( _) if read_at_least_one_message => Ok ( ( ) ) ,
329- Ok ( output) if output. status . success ( ) => Ok ( ( ) ) ,
330- Ok ( output) => {
331- Err ( io:: Error :: new ( io:: ErrorKind :: Other , format ! (
332- "Cargo watcher failed, the command produced no valid metadata (exit code: {:?}):\n {}" ,
333- output. status, error
334- ) ) )
335- }
364+ Ok ( _) => Ok ( ( read_at_least_one_message, error) ) ,
336365 Err ( e) => Err ( io:: Error :: new ( e. kind ( ) , format ! ( "{:?}: {}" , e, error) ) ) ,
337366 }
338367 }
0 commit comments