5
5
use std:: {
6
6
fmt,
7
7
io:: { self , BufReader } ,
8
+ ops,
8
9
path:: PathBuf ,
9
- process:: { Command , Stdio } ,
10
+ process:: { self , Command , Stdio } ,
10
11
time:: Duration ,
11
12
} ;
12
13
@@ -49,8 +50,8 @@ impl fmt::Display for FlycheckConfig {
49
50
#[ derive( Debug ) ]
50
51
pub struct FlycheckHandle {
51
52
// XXX: drop order is significant
52
- cmd_send : Sender < Restart > ,
53
- handle : jod_thread:: JoinHandle ,
53
+ sender : Sender < Restart > ,
54
+ thread : jod_thread:: JoinHandle ,
54
55
}
55
56
56
57
impl FlycheckHandle {
@@ -59,16 +60,15 @@ impl FlycheckHandle {
59
60
config : FlycheckConfig ,
60
61
workspace_root : PathBuf ,
61
62
) -> FlycheckHandle {
62
- let ( cmd_send, cmd_recv) = unbounded :: < Restart > ( ) ;
63
- let handle = jod_thread:: spawn ( move || {
64
- FlycheckActor :: new ( sender, config, workspace_root) . run ( cmd_recv) ;
65
- } ) ;
66
- FlycheckHandle { cmd_send, handle }
63
+ let actor = FlycheckActor :: new ( sender, config, workspace_root) ;
64
+ let ( sender, receiver) = unbounded :: < Restart > ( ) ;
65
+ let thread = jod_thread:: spawn ( move || actor. run ( receiver) ) ;
66
+ FlycheckHandle { sender, thread }
67
67
}
68
68
69
69
/// Schedule a re-start of the cargo check worker.
70
70
pub fn update ( & self ) {
71
- self . cmd_send . send ( Restart ) . unwrap ( ) ;
71
+ self . sender . send ( Restart ) . unwrap ( ) ;
72
72
}
73
73
}
74
74
@@ -85,7 +85,7 @@ pub enum Message {
85
85
pub enum Progress {
86
86
DidStart ,
87
87
DidCheckCrate ( String ) ,
88
- DidFinish ,
88
+ DidFinish ( io :: Result < ( ) > ) ,
89
89
DidCancel ,
90
90
}
91
91
@@ -100,8 +100,7 @@ struct FlycheckActor {
100
100
/// doesn't provide a way to read sub-process output without blocking, so we
101
101
/// have to wrap sub-processes output handling in a thread and pass messages
102
102
/// back over a channel.
103
- // XXX: drop order is significant
104
- check_process : Option < ( Receiver < cargo_metadata:: Message > , jod_thread:: JoinHandle ) > ,
103
+ cargo_handle : Option < CargoHandle > ,
105
104
}
106
105
107
106
enum Event {
@@ -115,29 +114,36 @@ impl FlycheckActor {
115
114
config : FlycheckConfig ,
116
115
workspace_root : PathBuf ,
117
116
) -> FlycheckActor {
118
- FlycheckActor { sender, config, workspace_root, check_process : None }
117
+ FlycheckActor { sender, config, workspace_root, cargo_handle : None }
119
118
}
120
119
fn next_event ( & self , inbox : & Receiver < Restart > ) -> Option < Event > {
121
- let check_chan = self . check_process . as_ref ( ) . map ( |( chan , _thread ) | chan ) ;
120
+ let check_chan = self . cargo_handle . as_ref ( ) . map ( |cargo| & cargo . receiver ) ;
122
121
select ! {
123
122
recv( inbox) -> msg => msg. ok( ) . map( Event :: Restart ) ,
124
123
recv( check_chan. unwrap_or( & never( ) ) ) -> msg => Some ( Event :: CheckEvent ( msg. ok( ) ) ) ,
125
124
}
126
125
}
127
- fn run ( & mut self , inbox : Receiver < Restart > ) {
126
+ fn run ( mut self , inbox : Receiver < Restart > ) {
128
127
while let Some ( event) = self . next_event ( & inbox) {
129
128
match event {
130
129
Event :: Restart ( Restart ) => {
131
130
while let Ok ( Restart ) = inbox. recv_timeout ( Duration :: from_millis ( 50 ) ) { }
131
+
132
132
self . cancel_check_process ( ) ;
133
- self . check_process = Some ( self . start_check_process ( ) ) ;
134
- self . send ( Message :: Progress ( Progress :: DidStart ) ) ;
133
+
134
+ let mut command = self . check_command ( ) ;
135
+ command. stdout ( Stdio :: piped ( ) ) . stderr ( Stdio :: null ( ) ) . stdin ( Stdio :: null ( ) ) ;
136
+ if let Ok ( child) = command. spawn ( ) . map ( JodChild ) {
137
+ self . cargo_handle = Some ( CargoHandle :: spawn ( child) ) ;
138
+ self . send ( Message :: Progress ( Progress :: DidStart ) ) ;
139
+ }
135
140
}
136
141
Event :: CheckEvent ( None ) => {
137
142
// Watcher finished, replace it with a never channel to
138
143
// avoid busy-waiting.
139
- assert ! ( self . check_process. take( ) . is_some( ) ) ;
140
- self . send ( Message :: Progress ( Progress :: DidFinish ) ) ;
144
+ let cargo_handle = self . cargo_handle . take ( ) . unwrap ( ) ;
145
+ let res = cargo_handle. join ( ) ;
146
+ self . send ( Message :: Progress ( Progress :: DidFinish ( res) ) ) ;
141
147
}
142
148
Event :: CheckEvent ( Some ( message) ) => match message {
143
149
cargo_metadata:: Message :: CompilerArtifact ( msg) => {
@@ -162,11 +168,11 @@ impl FlycheckActor {
162
168
self . cancel_check_process ( ) ;
163
169
}
164
170
fn cancel_check_process ( & mut self ) {
165
- if self . check_process . take ( ) . is_some ( ) {
171
+ if self . cargo_handle . take ( ) . is_some ( ) {
166
172
self . send ( Message :: Progress ( Progress :: DidCancel ) ) ;
167
173
}
168
174
}
169
- fn start_check_process ( & self ) -> ( Receiver < cargo_metadata :: Message > , jod_thread :: JoinHandle ) {
175
+ fn check_command ( & self ) -> Command {
170
176
let mut cmd = match & self . config {
171
177
FlycheckConfig :: CargoCommand {
172
178
command,
@@ -198,88 +204,112 @@ impl FlycheckActor {
198
204
}
199
205
} ;
200
206
cmd. current_dir ( & self . workspace_root ) ;
201
-
202
- let ( message_send, message_recv) = unbounded ( ) ;
203
- let thread = jod_thread:: spawn ( move || {
204
- // If we trigger an error here, we will do so in the loop instead,
205
- // which will break out of the loop, and continue the shutdown
206
- let res = run_cargo ( cmd, & mut |message| {
207
- // Skip certain kinds of messages to only spend time on what's useful
208
- match & message {
209
- cargo_metadata:: Message :: CompilerArtifact ( artifact) if artifact. fresh => {
210
- return true
211
- }
212
- cargo_metadata:: Message :: BuildScriptExecuted ( _)
213
- | cargo_metadata:: Message :: Unknown => return true ,
214
- _ => { }
215
- }
216
-
217
- // if the send channel was closed, we want to shutdown
218
- message_send. send ( message) . is_ok ( )
219
- } ) ;
220
-
221
- if let Err ( err) = res {
222
- // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>`
223
- // to display user-caused misconfiguration errors instead of just logging them here
224
- log:: error!( "Cargo watcher failed {:?}" , err) ;
225
- }
226
- } ) ;
227
- ( message_recv, thread)
207
+ cmd
228
208
}
229
209
230
210
fn send ( & self , check_task : Message ) {
231
211
( self . sender ) ( check_task)
232
212
}
233
213
}
234
214
235
- fn run_cargo (
236
- mut command : Command ,
237
- on_message : & mut dyn FnMut ( cargo_metadata :: Message ) -> bool ,
238
- ) -> io:: Result < ( ) > {
239
- let mut child =
240
- command . stdout ( Stdio :: piped ( ) ) . stderr ( Stdio :: null ( ) ) . stdin ( Stdio :: null ( ) ) . spawn ( ) ? ;
215
+ struct CargoHandle {
216
+ child : JodChild ,
217
+ # [ allow ( unused ) ]
218
+ thread : jod_thread :: JoinHandle < io:: Result < bool > > ,
219
+ receiver : Receiver < cargo_metadata :: Message > ,
220
+ }
241
221
242
- // We manually read a line at a time, instead of using serde's
243
- // stream deserializers, because the deserializer cannot recover
244
- // from an error, resulting in it getting stuck, because we try to
245
- // be resillient against failures.
246
- //
247
- // Because cargo only outputs one JSON object per line, we can
248
- // simply skip a line if it doesn't parse, which just ignores any
249
- // erroneus output.
250
- let stdout = BufReader :: new ( child. stdout . take ( ) . unwrap ( ) ) ;
251
- let mut read_at_least_one_message = false ;
252
- for message in cargo_metadata:: Message :: parse_stream ( stdout) {
253
- let message = match message {
254
- Ok ( message) => message,
255
- Err ( err) => {
256
- log:: error!( "Invalid json from cargo check, ignoring ({})" , err) ;
257
- continue ;
258
- }
259
- } ;
222
+ impl CargoHandle {
223
+ fn spawn ( mut child : JodChild ) -> CargoHandle {
224
+ let child_stdout = child. stdout . take ( ) . unwrap ( ) ;
225
+ let ( sender, receiver) = unbounded ( ) ;
226
+ let actor = CargoActor :: new ( child_stdout, sender) ;
227
+ let thread = jod_thread:: spawn ( move || actor. run ( ) ) ;
228
+ CargoHandle { child, thread, receiver }
229
+ }
230
+ fn join ( mut self ) -> io:: Result < ( ) > {
231
+ // It is okay to ignore the result, as it only errors if the process is already dead
232
+ let _ = self . child . kill ( ) ;
233
+ let exit_status = self . child . wait ( ) ?;
234
+ let read_at_least_one_message = self . thread . join ( ) ?;
235
+ if !exit_status. success ( ) && !read_at_least_one_message {
236
+ // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment:
237
+ // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298
238
+ return Err ( io:: Error :: new (
239
+ io:: ErrorKind :: Other ,
240
+ format ! (
241
+ "Cargo watcher failed,the command produced no valid metadata (exit code: {:?})" ,
242
+ exit_status
243
+ ) ,
244
+ ) ) ;
245
+ }
246
+ Ok ( ( ) )
247
+ }
248
+ }
249
+
250
+ struct CargoActor {
251
+ child_stdout : process:: ChildStdout ,
252
+ sender : Sender < cargo_metadata:: Message > ,
253
+ }
254
+
255
+ impl CargoActor {
256
+ fn new (
257
+ child_stdout : process:: ChildStdout ,
258
+ sender : Sender < cargo_metadata:: Message > ,
259
+ ) -> CargoActor {
260
+ CargoActor { child_stdout, sender }
261
+ }
262
+ fn run ( self ) -> io:: Result < bool > {
263
+ // We manually read a line at a time, instead of using serde's
264
+ // stream deserializers, because the deserializer cannot recover
265
+ // from an error, resulting in it getting stuck, because we try to
266
+ // be resilient against failures.
267
+ //
268
+ // Because cargo only outputs one JSON object per line, we can
269
+ // simply skip a line if it doesn't parse, which just ignores any
270
+ // erroneus output.
271
+ let stdout = BufReader :: new ( self . child_stdout ) ;
272
+ let mut read_at_least_one_message = false ;
273
+ for message in cargo_metadata:: Message :: parse_stream ( stdout) {
274
+ let message = match message {
275
+ Ok ( message) => message,
276
+ Err ( err) => {
277
+ log:: error!( "Invalid json from cargo check, ignoring ({})" , err) ;
278
+ continue ;
279
+ }
280
+ } ;
260
281
261
- read_at_least_one_message = true ;
282
+ read_at_least_one_message = true ;
262
283
263
- if !on_message ( message) {
264
- break ;
284
+ // Skip certain kinds of messages to only spend time on what's useful
285
+ match & message {
286
+ cargo_metadata:: Message :: CompilerArtifact ( artifact) if artifact. fresh => ( ) ,
287
+ cargo_metadata:: Message :: BuildScriptExecuted ( _)
288
+ | cargo_metadata:: Message :: Unknown => ( ) ,
289
+ _ => self . sender . send ( message) . unwrap ( ) ,
290
+ }
265
291
}
292
+ Ok ( read_at_least_one_message)
266
293
}
294
+ }
267
295
268
- // It is okay to ignore the result, as it only errors if the process is already dead
269
- let _ = child. kill ( ) ;
296
+ struct JodChild ( process:: Child ) ;
270
297
271
- let exit_status = child. wait ( ) ?;
272
- if !exit_status. success ( ) && !read_at_least_one_message {
273
- // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment:
274
- // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298
275
- return Err ( io:: Error :: new (
276
- io:: ErrorKind :: Other ,
277
- format ! (
278
- "the command produced no valid metadata (exit code: {:?}): {:?}" ,
279
- exit_status, command
280
- ) ,
281
- ) ) ;
298
+ impl ops:: Deref for JodChild {
299
+ type Target = process:: Child ;
300
+ fn deref ( & self ) -> & process:: Child {
301
+ & self . 0
282
302
}
303
+ }
283
304
284
- Ok ( ( ) )
305
+ impl ops:: DerefMut for JodChild {
306
+ fn deref_mut ( & mut self ) -> & mut process:: Child {
307
+ & mut self . 0
308
+ }
309
+ }
310
+
311
+ impl Drop for JodChild {
312
+ fn drop ( & mut self ) {
313
+ let _ = self . 0 . kill ( ) ;
314
+ }
285
315
}
0 commit comments