@@ -100,8 +100,7 @@ struct FlycheckActor {
100100 /// doesn't provide a way to read sub-process output without blocking, so we
101101 /// have to wrap sub-processes output handling in a thread and pass messages
102102 /// back over a channel.
103- // XXX: drop order is significant
104- check_process : Option < ( Receiver < cargo_metadata:: Message > , jod_thread:: JoinHandle ) > ,
103+ check_process : Option < CargoHandle > ,
105104}
106105
107106enum Event {
@@ -118,7 +117,7 @@ impl FlycheckActor {
118117 FlycheckActor { sender, config, workspace_root, check_process : None }
119118 }
120119 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 . check_process . as_ref ( ) . map ( |cargo| & cargo . receiver ) ;
122121 select ! {
123122 recv( inbox) -> msg => msg. ok( ) . map( Event :: Restart ) ,
124123 recv( check_chan. unwrap_or( & never( ) ) ) -> msg => Some ( Event :: CheckEvent ( msg. ok( ) ) ) ,
@@ -166,7 +165,7 @@ impl FlycheckActor {
166165 self . send ( Message :: Progress ( Progress :: DidCancel ) ) ;
167166 }
168167 }
169- fn start_check_process ( & self ) -> ( Receiver < cargo_metadata :: Message > , jod_thread :: JoinHandle ) {
168+ fn start_check_process ( & self ) -> CargoHandle {
170169 let mut cmd = match & self . config {
171170 FlycheckConfig :: CargoCommand {
172171 command,
@@ -199,90 +198,98 @@ impl FlycheckActor {
199198 } ;
200199 cmd. current_dir ( & self . workspace_root ) ;
201200
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)
201+ CargoHandle :: spawn ( cmd)
228202 }
229203
230204 fn send ( & self , check_task : Message ) {
231205 ( self . sender ) ( check_task)
232206 }
233207}
234208
235- fn run_cargo (
236- mut command : Command ,
237- on_message : & mut dyn FnMut ( cargo_metadata:: Message ) -> bool ,
238- ) -> io:: Result < ( ) > {
239- let child =
240- command. stdout ( Stdio :: piped ( ) ) . stderr ( Stdio :: null ( ) ) . stdin ( Stdio :: null ( ) ) . spawn ( ) ?;
241- let mut child = ChildKiller ( child) ;
242-
243- // We manually read a line at a time, instead of using serde's
244- // stream deserializers, because the deserializer cannot recover
245- // from an error, resulting in it getting stuck, because we try to
246- // be resillient against failures.
247- //
248- // Because cargo only outputs one JSON object per line, we can
249- // simply skip a line if it doesn't parse, which just ignores any
250- // erroneus output.
251- let stdout = BufReader :: new ( child. stdout . take ( ) . unwrap ( ) ) ;
252- let mut read_at_least_one_message = false ;
253- for message in cargo_metadata:: Message :: parse_stream ( stdout) {
254- let message = match message {
255- Ok ( message) => message,
256- Err ( err) => {
257- log:: error!( "Invalid json from cargo check, ignoring ({})" , err) ;
258- continue ;
259- }
260- } ;
261-
262- read_at_least_one_message = true ;
209+ struct CargoHandle {
210+ receiver : Receiver < cargo_metadata:: Message > ,
211+ #[ allow( unused) ]
212+ thread : jod_thread:: JoinHandle ,
213+ }
263214
264- if !on_message ( message) {
265- break ;
266- }
215+ impl CargoHandle {
216+ fn spawn ( command : Command ) -> CargoHandle {
217+ let ( sender, receiver) = unbounded ( ) ;
218+ let actor = CargoActor :: new ( command, sender) ;
219+ let thread = jod_thread:: spawn ( move || {
220+ let _ = actor. run ( ) ;
221+ } ) ;
222+ CargoHandle { receiver, thread }
267223 }
224+ }
268225
269- // It is okay to ignore the result, as it only errors if the process is already dead
270- let _ = child. kill ( ) ;
271-
272- let exit_status = child. wait ( ) ?;
273- if !exit_status. success ( ) && !read_at_least_one_message {
274- // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment:
275- // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298
276- return Err ( io:: Error :: new (
277- io:: ErrorKind :: Other ,
278- format ! (
279- "the command produced no valid metadata (exit code: {:?}): {:?}" ,
280- exit_status, command
281- ) ,
282- ) ) ;
226+ struct CargoActor {
227+ command : Command ,
228+ sender : Sender < cargo_metadata:: Message > ,
229+ }
230+
231+ impl CargoActor {
232+ fn new ( command : Command , sender : Sender < cargo_metadata:: Message > ) -> CargoActor {
233+ CargoActor { command, sender }
283234 }
235+ fn run ( mut self ) -> io:: Result < ( ) > {
236+ let child = self
237+ . command
238+ . stdout ( Stdio :: piped ( ) )
239+ . stderr ( Stdio :: null ( ) )
240+ . stdin ( Stdio :: null ( ) )
241+ . spawn ( ) ?;
242+ let mut child = ChildKiller ( child) ;
243+
244+ // We manually read a line at a time, instead of using serde's
245+ // stream deserializers, because the deserializer cannot recover
246+ // from an error, resulting in it getting stuck, because we try to
247+ // be resillient against failures.
248+ //
249+ // Because cargo only outputs one JSON object per line, we can
250+ // simply skip a line if it doesn't parse, which just ignores any
251+ // erroneus output.
252+ let stdout = BufReader :: new ( child. stdout . take ( ) . unwrap ( ) ) ;
253+ let mut read_at_least_one_message = false ;
254+ for message in cargo_metadata:: Message :: parse_stream ( stdout) {
255+ let message = match message {
256+ Ok ( message) => message,
257+ Err ( err) => {
258+ log:: error!( "Invalid json from cargo check, ignoring ({})" , err) ;
259+ continue ;
260+ }
261+ } ;
262+
263+ read_at_least_one_message = true ;
264+
265+ // Skip certain kinds of messages to only spend time on what's useful
266+ match & message {
267+ cargo_metadata:: Message :: CompilerArtifact ( artifact) if artifact. fresh => continue ,
268+ cargo_metadata:: Message :: BuildScriptExecuted ( _)
269+ | cargo_metadata:: Message :: Unknown => continue ,
270+ _ => {
271+ // if the send channel was closed, we want to shutdown
272+ if self . sender . send ( message) . is_err ( ) {
273+ break ;
274+ }
275+ }
276+ }
277+ }
278+
279+ // It is okay to ignore the result, as it only errors if the process is already dead
280+ let _ = child. kill ( ) ;
284281
285- Ok ( ( ) )
282+ let exit_status = child. wait ( ) ?;
283+ if !exit_status. success ( ) && !read_at_least_one_message {
284+ // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment:
285+ // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298
286+
287+ // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>`
288+ // to display user-caused misconfiguration errors instead of just logging them here
289+ log:: error!( "Cargo watcher failed,the command produced no valid metadata (exit code: {:?}): {:?}" , exit_status, self . command) ;
290+ }
291+ Ok ( ( ) )
292+ }
286293}
287294
288295struct ChildKiller ( process:: Child ) ;
0 commit comments