Skip to content

Commit 32e85a1

Browse files
committed
More standard pattern for Cargo
1 parent eddb744 commit 32e85a1

File tree

1 file changed

+83
-76
lines changed

1 file changed

+83
-76
lines changed

crates/flycheck/src/lib.rs

Lines changed: 83 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -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

107106
enum 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

288295
struct ChildKiller(process::Child);

0 commit comments

Comments
 (0)