diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/flycheck/src/lib.rs | 159 |
1 files changed, 83 insertions, 76 deletions
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index 073bcc9ae..ab1d71b98 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs | |||
@@ -100,8 +100,7 @@ struct FlycheckActor { | |||
100 | /// doesn't provide a way to read sub-process output without blocking, so we | 100 | /// doesn't provide a way to read sub-process output without blocking, so we |
101 | /// have to wrap sub-processes output handling in a thread and pass messages | 101 | /// have to wrap sub-processes output handling in a thread and pass messages |
102 | /// back over a channel. | 102 | /// back over a channel. |
103 | // XXX: drop order is significant | 103 | check_process: Option<CargoHandle>, |
104 | check_process: Option<(Receiver<cargo_metadata::Message>, jod_thread::JoinHandle)>, | ||
105 | } | 104 | } |
106 | 105 | ||
107 | enum Event { | 106 | enum Event { |
@@ -118,7 +117,7 @@ impl FlycheckActor { | |||
118 | FlycheckActor { sender, config, workspace_root, check_process: None } | 117 | FlycheckActor { sender, config, workspace_root, check_process: None } |
119 | } | 118 | } |
120 | fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> { | 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.check_process.as_ref().map(|cargo| &cargo.receiver); |
122 | select! { | 121 | select! { |
123 | recv(inbox) -> msg => msg.ok().map(Event::Restart), | 122 | recv(inbox) -> msg => msg.ok().map(Event::Restart), |
124 | recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())), | 123 | recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())), |
@@ -166,7 +165,7 @@ impl FlycheckActor { | |||
166 | self.send(Message::Progress(Progress::DidCancel)); | 165 | self.send(Message::Progress(Progress::DidCancel)); |
167 | } | 166 | } |
168 | } | 167 | } |
169 | fn start_check_process(&self) -> (Receiver<cargo_metadata::Message>, jod_thread::JoinHandle) { | 168 | fn start_check_process(&self) -> CargoHandle { |
170 | let mut cmd = match &self.config { | 169 | let mut cmd = match &self.config { |
171 | FlycheckConfig::CargoCommand { | 170 | FlycheckConfig::CargoCommand { |
172 | command, | 171 | command, |
@@ -199,32 +198,7 @@ impl FlycheckActor { | |||
199 | }; | 198 | }; |
200 | cmd.current_dir(&self.workspace_root); | 199 | cmd.current_dir(&self.workspace_root); |
201 | 200 | ||
202 | let (message_send, message_recv) = unbounded(); | 201 | CargoHandle::spawn(cmd) |
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) | ||
228 | } | 202 | } |
229 | 203 | ||
230 | fn send(&self, check_task: Message) { | 204 | fn send(&self, check_task: Message) { |
@@ -232,57 +206,90 @@ impl FlycheckActor { | |||
232 | } | 206 | } |
233 | } | 207 | } |
234 | 208 | ||
235 | fn run_cargo( | 209 | struct CargoHandle { |
236 | mut command: Command, | 210 | receiver: Receiver<cargo_metadata::Message>, |
237 | on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, | 211 | #[allow(unused)] |
238 | ) -> io::Result<()> { | 212 | thread: jod_thread::JoinHandle, |
239 | let child = | 213 | } |
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; | ||
263 | 214 | ||
264 | if !on_message(message) { | 215 | impl CargoHandle { |
265 | break; | 216 | fn spawn(command: Command) -> CargoHandle { |
266 | } | 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 } | ||
267 | } | 223 | } |
224 | } | ||
268 | 225 | ||
269 | // It is okay to ignore the result, as it only errors if the process is already dead | 226 | struct CargoActor { |
270 | let _ = child.kill(); | 227 | command: Command, |
271 | 228 | sender: Sender<cargo_metadata::Message>, | |
272 | let exit_status = child.wait()?; | 229 | } |
273 | if !exit_status.success() && !read_at_least_one_message { | 230 | |
274 | // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment: | 231 | impl CargoActor { |
275 | // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298 | 232 | fn new(command: Command, sender: Sender<cargo_metadata::Message>) -> CargoActor { |
276 | return Err(io::Error::new( | 233 | CargoActor { command, sender } |
277 | io::ErrorKind::Other, | ||
278 | format!( | ||
279 | "the command produced no valid metadata (exit code: {:?}): {:?}", | ||
280 | exit_status, command | ||
281 | ), | ||
282 | )); | ||
283 | } | 234 | } |
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(); | ||
284 | 281 | ||
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 | } | ||
286 | } | 293 | } |
287 | 294 | ||
288 | struct ChildKiller(process::Child); | 295 | struct ChildKiller(process::Child); |