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,18 +157,36 @@ impl FlycheckActor {
153157 while let Some ( event) = self . next_event ( & inbox) {
154158 match event {
155159 Event :: Restart ( Restart ) => {
160+ if let Some ( cargo_handle) = self . cargo_handle . take ( ) {
161+ // Cancel the previously spawned process
162+ cargo_handle. cancel ( ) ;
163+ }
156164 while let Ok ( Restart ) = inbox. recv_timeout ( Duration :: from_millis ( 50 ) ) { }
157-
158- self . cancel_check_process ( ) ;
165+ self . progress ( Progress :: DidCancel ) ;
159166
160167 let command = self . check_command ( ) ;
161- tracing:: info!( "restart flycheck {:?}" , command) ;
162- self . cargo_handle = Some ( CargoHandle :: spawn ( command) ) ;
163- self . progress ( Progress :: DidStart ) ;
168+ tracing:: debug!( ?command, "will restart flycheck" ) ;
169+ match CargoHandle :: spawn ( command) {
170+ Ok ( cargo_handle) => {
171+ tracing:: debug!(
172+ command = ?self . check_command( ) ,
173+ "did restart flycheck"
174+ ) ;
175+ self . cargo_handle = Some ( cargo_handle) ;
176+ self . progress ( Progress :: DidStart ) ;
177+ }
178+ Err ( error) => {
179+ tracing:: error!(
180+ command = ?self . check_command( ) ,
181+ %error, "failed to restart flycheck"
182+ ) ;
183+ }
184+ }
164185 }
165186 Event :: CheckEvent ( None ) => {
166- // Watcher finished, replace it with a never channel to
167- // avoid busy-waiting.
187+ tracing:: debug!( "flycheck finished" ) ;
188+
189+ // Watcher finished
168190 let cargo_handle = self . cargo_handle . take ( ) . unwrap ( ) ;
169191 let res = cargo_handle. join ( ) ;
170192 if res. is_err ( ) {
@@ -192,8 +214,10 @@ impl FlycheckActor {
192214 // If we rerun the thread, we need to discard the previous check results first
193215 self . cancel_check_process ( ) ;
194216 }
217+
195218 fn cancel_check_process ( & mut self ) {
196- if self . cargo_handle . take ( ) . is_some ( ) {
219+ if let Some ( cargo_handle) = self . cargo_handle . take ( ) {
220+ cargo_handle. cancel ( ) ;
197221 self . progress ( Progress :: DidCancel ) ;
198222 }
199223 }
@@ -249,37 +273,64 @@ impl FlycheckActor {
249273 }
250274}
251275
276+ /// A handle to a cargo process used for fly-checking.
252277struct CargoHandle {
253- thread : jod_thread:: JoinHandle < io:: Result < ( ) > > ,
278+ /// The handle to the actual cargo process. As we cannot cancel directly from with
279+ /// a read syscall dropping and therefor terminating the process is our best option.
280+ child : JodChild ,
281+ thread : jod_thread:: JoinHandle < io:: Result < ( bool , String ) > > ,
254282 receiver : Receiver < CargoMessage > ,
255283}
256284
257285impl CargoHandle {
258- fn spawn ( command : Command ) -> CargoHandle {
286+ fn spawn ( mut command : Command ) -> std:: io:: Result < CargoHandle > {
287+ command. stdout ( Stdio :: piped ( ) ) . stderr ( Stdio :: piped ( ) ) . stdin ( Stdio :: null ( ) ) ;
288+ let mut child = JodChild :: spawn ( command) ?;
289+
290+ let stdout = child. stdout . take ( ) . unwrap ( ) ;
291+ let stderr = child. stderr . take ( ) . unwrap ( ) ;
292+
259293 let ( sender, receiver) = unbounded ( ) ;
260- let actor = CargoActor :: new ( sender) ;
294+ let actor = CargoActor :: new ( sender, stdout , stderr ) ;
261295 let thread = jod_thread:: Builder :: new ( )
262296 . name ( "CargoHandle" . to_owned ( ) )
263- . spawn ( move || actor. run ( command ) )
297+ . spawn ( move || actor. run ( ) )
264298 . expect ( "failed to spawn thread" ) ;
265- CargoHandle { thread, receiver }
299+ Ok ( CargoHandle { child , thread, receiver } )
266300 }
267301
268- fn join ( self ) -> io:: Result < ( ) > {
269- self . thread . join ( )
302+ fn cancel ( mut self ) {
303+ let _ = self . child . kill ( ) ;
304+ let _ = self . child . wait ( ) ;
305+ }
306+
307+ fn join ( mut self ) -> io:: Result < ( ) > {
308+ let _ = self . child . kill ( ) ;
309+ let exit_status = self . child . wait ( ) ?;
310+ let ( read_at_least_one_message, error) = self . thread . join ( ) ?;
311+ if read_at_least_one_message || exit_status. success ( ) {
312+ Ok ( ( ) )
313+ } else {
314+ Err ( io:: Error :: new ( io:: ErrorKind :: Other , format ! (
315+ "Cargo watcher failed, the command produced no valid metadata (exit code: {:?}):\n {}" ,
316+ exit_status, error
317+ ) ) )
318+ }
270319 }
271320}
272321
273322struct CargoActor {
274323 sender : Sender < CargoMessage > ,
324+ stdout : ChildStdout ,
325+ stderr : ChildStderr ,
275326}
276327
277328impl CargoActor {
278- fn new ( sender : Sender < CargoMessage > ) -> CargoActor {
279- CargoActor { sender }
329+ fn new ( sender : Sender < CargoMessage > , stdout : ChildStdout , stderr : ChildStderr ) -> CargoActor {
330+ CargoActor { sender, stdout , stderr }
280331 }
281332
282- fn run ( self , command : Command ) -> io:: Result < ( ) > {
333+ fn run ( self ) -> io:: Result < ( bool , String ) > {
283334 // We manually read a line at a time, instead of using serde's
284335 // stream deserializers, because the deserializer cannot recover
285336 // from an error, resulting in it getting stuck, because we try to
@@ -292,7 +343,8 @@ impl CargoActor {
292343 let mut error = String :: new ( ) ;
293344 let mut read_at_least_one_message = false ;
294345 let output = streaming_output (
295- command,
346+ self . stdout ,
347+ self . stderr ,
296348 & mut |line| {
297349 read_at_least_one_message = true ;
298350
@@ -325,14 +377,7 @@ impl CargoActor {
325377 } ,
326378 ) ;
327379 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- }
380+ Ok ( _) => Ok ( ( read_at_least_one_message, error) ) ,
336381 Err ( e) => Err ( io:: Error :: new ( e. kind ( ) , format ! ( "{:?}: {}" , e, error) ) ) ,
337382 }
338383 }
0 commit comments