Skip to content

Commit 5cdd8d4

Browse files
committed
Cleanup cargo process handling in flycheck
1 parent 32e85a1 commit 5cdd8d4

File tree

2 files changed

+66
-61
lines changed

2 files changed

+66
-61
lines changed

crates/flycheck/src/lib.rs

Lines changed: 61 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ pub enum Message {
8585
pub enum Progress {
8686
DidStart,
8787
DidCheckCrate(String),
88-
DidFinish,
88+
DidFinish(io::Result<()>),
8989
DidCancel,
9090
}
9191

@@ -100,7 +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-
check_process: Option<CargoHandle>,
103+
cargo_handle: Option<CargoHandle>,
104104
}
105105

106106
enum Event {
@@ -114,10 +114,10 @@ impl FlycheckActor {
114114
config: FlycheckConfig,
115115
workspace_root: PathBuf,
116116
) -> FlycheckActor {
117-
FlycheckActor { sender, config, workspace_root, check_process: None }
117+
FlycheckActor { sender, config, workspace_root, cargo_handle: None }
118118
}
119119
fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
120-
let check_chan = self.check_process.as_ref().map(|cargo| &cargo.receiver);
120+
let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver);
121121
select! {
122122
recv(inbox) -> msg => msg.ok().map(Event::Restart),
123123
recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
@@ -128,15 +128,22 @@ impl FlycheckActor {
128128
match event {
129129
Event::Restart(Restart) => {
130130
while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {}
131+
131132
self.cancel_check_process();
132-
self.check_process = Some(self.start_check_process());
133-
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+
}
134140
}
135141
Event::CheckEvent(None) => {
136142
// Watcher finished, replace it with a never channel to
137143
// avoid busy-waiting.
138-
assert!(self.check_process.take().is_some());
139-
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)));
140147
}
141148
Event::CheckEvent(Some(message)) => match message {
142149
cargo_metadata::Message::CompilerArtifact(msg) => {
@@ -161,11 +168,11 @@ impl FlycheckActor {
161168
self.cancel_check_process();
162169
}
163170
fn cancel_check_process(&mut self) {
164-
if self.check_process.take().is_some() {
171+
if self.cargo_handle.take().is_some() {
165172
self.send(Message::Progress(Progress::DidCancel));
166173
}
167174
}
168-
fn start_check_process(&self) -> CargoHandle {
175+
fn check_command(&self) -> Command {
169176
let mut cmd = match &self.config {
170177
FlycheckConfig::CargoCommand {
171178
command,
@@ -197,8 +204,7 @@ impl FlycheckActor {
197204
}
198205
};
199206
cmd.current_dir(&self.workspace_root);
200-
201-
CargoHandle::spawn(cmd)
207+
cmd
202208
}
203209

204210
fn send(&self, check_task: Message) {
@@ -207,49 +213,62 @@ impl FlycheckActor {
207213
}
208214

209215
struct CargoHandle {
210-
receiver: Receiver<cargo_metadata::Message>,
216+
child: JodChild,
211217
#[allow(unused)]
212-
thread: jod_thread::JoinHandle,
218+
thread: jod_thread::JoinHandle<io::Result<bool>>,
219+
receiver: Receiver<cargo_metadata::Message>,
213220
}
214221

215222
impl CargoHandle {
216-
fn spawn(command: Command) -> CargoHandle {
223+
fn spawn(mut child: JodChild) -> CargoHandle {
224+
let child_stdout = child.stdout.take().unwrap();
217225
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 }
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(())
223247
}
224248
}
225249

226250
struct CargoActor {
227-
command: Command,
251+
child_stdout: process::ChildStdout,
228252
sender: Sender<cargo_metadata::Message>,
229253
}
230254

231255
impl CargoActor {
232-
fn new(command: Command, sender: Sender<cargo_metadata::Message>) -> CargoActor {
233-
CargoActor { command, sender }
256+
fn new(
257+
child_stdout: process::ChildStdout,
258+
sender: Sender<cargo_metadata::Message>,
259+
) -> CargoActor {
260+
CargoActor { child_stdout, sender }
234261
}
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-
262+
fn run(self) -> io::Result<bool> {
244263
// We manually read a line at a time, instead of using serde's
245264
// stream deserializers, because the deserializer cannot recover
246265
// from an error, resulting in it getting stuck, because we try to
247-
// be resillient against failures.
266+
// be resilient against failures.
248267
//
249268
// Because cargo only outputs one JSON object per line, we can
250269
// simply skip a line if it doesn't parse, which just ignores any
251270
// erroneus output.
252-
let stdout = BufReader::new(child.stdout.take().unwrap());
271+
let stdout = BufReader::new(self.child_stdout);
253272
let mut read_at_least_one_message = false;
254273
for message in cargo_metadata::Message::parse_stream(stdout) {
255274
let message = match message {
@@ -264,50 +283,32 @@ impl CargoActor {
264283

265284
// Skip certain kinds of messages to only spend time on what's useful
266285
match &message {
267-
cargo_metadata::Message::CompilerArtifact(artifact) if artifact.fresh => continue,
286+
cargo_metadata::Message::CompilerArtifact(artifact) if artifact.fresh => (),
268287
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-
}
288+
| cargo_metadata::Message::Unknown => (),
289+
_ => self.sender.send(message).unwrap(),
276290
}
277291
}
278-
279-
// It is okay to ignore the result, as it only errors if the process is already dead
280-
let _ = child.kill();
281-
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+
Ok(read_at_least_one_message)
292293
}
293294
}
294295

295-
struct ChildKiller(process::Child);
296+
struct JodChild(process::Child);
296297

297-
impl ops::Deref for ChildKiller {
298+
impl ops::Deref for JodChild {
298299
type Target = process::Child;
299300
fn deref(&self) -> &process::Child {
300301
&self.0
301302
}
302303
}
303304

304-
impl ops::DerefMut for ChildKiller {
305+
impl ops::DerefMut for JodChild {
305306
fn deref_mut(&mut self) -> &mut process::Child {
306307
&mut self.0
307308
}
308309
}
309310

310-
impl Drop for ChildKiller {
311+
impl Drop for JodChild {
311312
fn drop(&mut self) {
312313
let _ = self.0.kill();
313314
}

crates/rust-analyzer/src/main_loop.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,11 @@ impl GlobalState {
216216
flycheck::Progress::DidCheckCrate(target) => {
217217
(Progress::Report, Some(target))
218218
}
219-
flycheck::Progress::DidFinish | flycheck::Progress::DidCancel => {
219+
flycheck::Progress::DidCancel => (Progress::End, None),
220+
flycheck::Progress::DidFinish(result) => {
221+
if let Err(err) = result {
222+
log::error!("cargo check failed: {}", err)
223+
}
220224
(Progress::End, None)
221225
}
222226
};

0 commit comments

Comments
 (0)