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,18 +157,36 @@ impl FlycheckActor {
153
157
while let Some ( event) = self . next_event ( & inbox) {
154
158
match event {
155
159
Event :: Restart ( Restart ) => {
160
+ if let Some ( cargo_handle) = self . cargo_handle . take ( ) {
161
+ // Cancel the previously spawned process
162
+ cargo_handle. cancel ( ) ;
163
+ }
156
164
while let Ok ( Restart ) = inbox. recv_timeout ( Duration :: from_millis ( 50 ) ) { }
157
-
158
- self . cancel_check_process ( ) ;
165
+ self . progress ( Progress :: DidCancel ) ;
159
166
160
167
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
+ }
164
185
}
165
186
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
168
190
let cargo_handle = self . cargo_handle . take ( ) . unwrap ( ) ;
169
191
let res = cargo_handle. join ( ) ;
170
192
if res. is_err ( ) {
@@ -192,8 +214,10 @@ impl FlycheckActor {
192
214
// If we rerun the thread, we need to discard the previous check results first
193
215
self . cancel_check_process ( ) ;
194
216
}
217
+
195
218
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 ( ) ;
197
221
self . progress ( Progress :: DidCancel ) ;
198
222
}
199
223
}
@@ -249,37 +273,64 @@ impl FlycheckActor {
249
273
}
250
274
}
251
275
276
+ /// A handle to a cargo process used for fly-checking.
252
277
struct 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 ) > > ,
254
282
receiver : Receiver < CargoMessage > ,
255
283
}
256
284
257
285
impl 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
+
259
293
let ( sender, receiver) = unbounded ( ) ;
260
- let actor = CargoActor :: new ( sender) ;
294
+ let actor = CargoActor :: new ( sender, stdout , stderr ) ;
261
295
let thread = jod_thread:: Builder :: new ( )
262
296
. name ( "CargoHandle" . to_owned ( ) )
263
- . spawn ( move || actor. run ( command ) )
297
+ . spawn ( move || actor. run ( ) )
264
298
. expect ( "failed to spawn thread" ) ;
265
- CargoHandle { thread, receiver }
299
+ Ok ( CargoHandle { child , thread, receiver } )
266
300
}
267
301
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
+ }
270
319
}
271
320
}
272
321
273
322
struct CargoActor {
274
323
sender : Sender < CargoMessage > ,
324
+ stdout : ChildStdout ,
325
+ stderr : ChildStderr ,
275
326
}
276
327
277
328
impl 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 }
280
331
}
281
332
282
- fn run ( self , command : Command ) -> io:: Result < ( ) > {
333
+ fn run ( self ) -> io:: Result < ( bool , String ) > {
283
334
// We manually read a line at a time, instead of using serde's
284
335
// stream deserializers, because the deserializer cannot recover
285
336
// from an error, resulting in it getting stuck, because we try to
@@ -292,7 +343,8 @@ impl CargoActor {
292
343
let mut error = String :: new ( ) ;
293
344
let mut read_at_least_one_message = false ;
294
345
let output = streaming_output (
295
- command,
346
+ self . stdout ,
347
+ self . stderr ,
296
348
& mut |line| {
297
349
read_at_least_one_message = true ;
298
350
@@ -325,14 +377,7 @@ impl CargoActor {
325
377
} ,
326
378
) ;
327
379
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) ) ,
336
381
Err ( e) => Err ( io:: Error :: new ( e. kind ( ) , format ! ( "{:?}: {}" , e, error) ) ) ,
337
382
}
338
383
}
0 commit comments