2
2
//! another compatible command (f.x. clippy) in a background thread and provide
3
3
//! LSP diagnostics based on the output of the command.
4
4
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
+ } ;
6
10
7
11
use crossbeam_channel:: { never, select, unbounded, Receiver , Sender } ;
8
12
use paths:: AbsPathBuf ;
9
13
use serde:: Deserialize ;
10
- use stdx:: process:: streaming_output;
14
+ use stdx:: { process:: streaming_output, JodChild } ;
11
15
12
16
pub use cargo_metadata:: diagnostic:: {
13
17
Applicability , Diagnostic , DiagnosticCode , DiagnosticLevel , DiagnosticSpan ,
@@ -117,7 +121,7 @@ struct FlycheckActor {
117
121
sender : Box < dyn Fn ( Message ) + Send > ,
118
122
config : FlycheckConfig ,
119
123
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
121
125
/// run `cargo check` without blocking. Currently the Rust standard library
122
126
/// doesn't provide a way to read sub-process output without blocking, so we
123
127
/// have to wrap sub-processes output handling in a thread and pass messages
@@ -153,14 +157,24 @@ impl FlycheckActor {
153
157
while let Some ( event) = self . next_event ( & inbox) {
154
158
match event {
155
159
Event :: Restart ( Restart ) => {
160
+ // Drop and cancel the previously spawned process
161
+ self . cargo_handle . take ( ) ;
156
162
while let Ok ( Restart ) = inbox. recv_timeout ( Duration :: from_millis ( 50 ) ) { }
157
163
158
164
self . cancel_check_process ( ) ;
159
165
160
166
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
+ }
164
178
}
165
179
Event :: CheckEvent ( None ) => {
166
180
// Watcher finished, replace it with a never channel to
@@ -249,37 +263,58 @@ impl FlycheckActor {
249
263
}
250
264
}
251
265
266
+ /// A handle to a cargo process used for fly-checking.
252
267
struct 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 ) > > ,
254
272
receiver : Receiver < CargoMessage > ,
255
273
}
256
274
257
275
impl 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
+
259
283
let ( sender, receiver) = unbounded ( ) ;
260
- let actor = CargoActor :: new ( sender) ;
284
+ let actor = CargoActor :: new ( sender, stdout , stderr ) ;
261
285
let thread = jod_thread:: Builder :: new ( )
262
286
. name ( "CargoHandle" . to_owned ( ) )
263
- . spawn ( move || actor. run ( command ) )
287
+ . spawn ( move || actor. run ( ) )
264
288
. expect ( "failed to spawn thread" ) ;
265
- CargoHandle { thread, receiver }
289
+ Ok ( CargoHandle { child , thread, receiver } )
266
290
}
267
291
268
292
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
+ }
270
303
}
271
304
}
272
305
273
306
struct CargoActor {
274
307
sender : Sender < CargoMessage > ,
308
+ stdout : ChildStdout ,
309
+ stderr : ChildStderr ,
275
310
}
276
311
277
312
impl 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 }
280
315
}
281
316
282
- fn run ( self , command : Command ) -> io:: Result < ( ) > {
317
+ fn run ( self ) -> io:: Result < ( bool , String ) > {
283
318
// We manually read a line at a time, instead of using serde's
284
319
// stream deserializers, because the deserializer cannot recover
285
320
// from an error, resulting in it getting stuck, because we try to
@@ -292,7 +327,8 @@ impl CargoActor {
292
327
let mut error = String :: new ( ) ;
293
328
let mut read_at_least_one_message = false ;
294
329
let output = streaming_output (
295
- command,
330
+ self . stdout ,
331
+ self . stderr ,
296
332
& mut |line| {
297
333
read_at_least_one_message = true ;
298
334
@@ -325,14 +361,7 @@ impl CargoActor {
325
361
} ,
326
362
) ;
327
363
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) ) ,
336
365
Err ( e) => Err ( io:: Error :: new ( e. kind ( ) , format ! ( "{:?}: {}" , e, error) ) ) ,
337
366
}
338
367
}
0 commit comments