Skip to content

Commit c86d8d4

Browse files
committed
Streamline flycheck implementation
1 parent b46fd38 commit c86d8d4

File tree

1 file changed

+68
-80
lines changed

1 file changed

+68
-80
lines changed

crates/ra_flycheck/src/lib.rs

Lines changed: 68 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,25 @@ pub enum CheckCommand {
7979
struct CheckWatcherThread {
8080
options: CheckConfig,
8181
workspace_root: PathBuf,
82-
watcher: WatchThread,
8382
last_update_req: Option<Instant>,
83+
// XXX: drop order is significant
84+
message_recv: Receiver<CheckEvent>,
85+
/// WatchThread exists to wrap around the communication needed to be able to
86+
/// run `cargo check` without blocking. Currently the Rust standard library
87+
/// doesn't provide a way to read sub-process output without blocking, so we
88+
/// have to wrap sub-processes output handling in a thread and pass messages
89+
/// back over a channel.
90+
check_process: Option<jod_thread::JoinHandle<()>>,
8491
}
8592

8693
impl CheckWatcherThread {
8794
fn new(options: CheckConfig, workspace_root: PathBuf) -> CheckWatcherThread {
8895
CheckWatcherThread {
8996
options,
9097
workspace_root,
91-
watcher: WatchThread::dummy(),
9298
last_update_req: None,
99+
message_recv: never(),
100+
check_process: None,
93101
}
94102
}
95103

@@ -106,25 +114,21 @@ impl CheckWatcherThread {
106114
break;
107115
},
108116
},
109-
recv(self.watcher.message_recv) -> msg => match msg {
117+
recv(self.message_recv) -> msg => match msg {
110118
Ok(msg) => self.handle_message(msg, task_send),
111119
Err(RecvError) => {
112120
// Watcher finished, replace it with a never channel to
113121
// avoid busy-waiting.
114-
std::mem::replace(&mut self.watcher.message_recv, never());
122+
self.message_recv = never();
123+
self.check_process = None;
115124
},
116125
}
117126
};
118127

119128
if self.should_recheck() {
120-
self.last_update_req.take();
129+
self.last_update_req = None;
121130
task_send.send(CheckTask::ClearDiagnostics).unwrap();
122-
123-
// Replace with a dummy watcher first so we drop the original and wait for completion
124-
std::mem::replace(&mut self.watcher, WatchThread::dummy());
125-
126-
// Then create the actual new watcher
127-
self.watcher = WatchThread::new(&self.options, &self.workspace_root);
131+
self.restart_check_process();
128132
}
129133
}
130134
}
@@ -207,6 +211,59 @@ impl CheckWatcherThread {
207211
CheckEvent::Msg(Message::Unknown) => {}
208212
}
209213
}
214+
215+
fn restart_check_process(&mut self) {
216+
// First, clear and cancel the old thread
217+
self.message_recv = never();
218+
self.check_process = None;
219+
if !self.options.enable {
220+
return;
221+
}
222+
223+
let mut args: Vec<String> = vec![
224+
self.options.command.clone(),
225+
"--workspace".to_string(),
226+
"--message-format=json".to_string(),
227+
"--manifest-path".to_string(),
228+
format!("{}/Cargo.toml", self.workspace_root.display()),
229+
];
230+
if self.options.all_targets {
231+
args.push("--all-targets".to_string());
232+
}
233+
args.extend(self.options.args.iter().cloned());
234+
235+
let (message_send, message_recv) = unbounded();
236+
let workspace_root = self.workspace_root.to_owned();
237+
self.message_recv = message_recv;
238+
self.check_process = Some(jod_thread::spawn(move || {
239+
// If we trigger an error here, we will do so in the loop instead,
240+
// which will break out of the loop, and continue the shutdown
241+
let _ = message_send.send(CheckEvent::Begin);
242+
243+
let res = run_cargo(&args, Some(&workspace_root), &mut |message| {
244+
// Skip certain kinds of messages to only spend time on what's useful
245+
match &message {
246+
Message::CompilerArtifact(artifact) if artifact.fresh => return true,
247+
Message::BuildScriptExecuted(_) => return true,
248+
Message::Unknown => return true,
249+
_ => {}
250+
}
251+
252+
// if the send channel was closed, we want to shutdown
253+
message_send.send(CheckEvent::Msg(message)).is_ok()
254+
});
255+
256+
if let Err(err) = res {
257+
// FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>`
258+
// to display user-caused misconfiguration errors instead of just logging them here
259+
log::error!("Cargo watcher failed {:?}", err);
260+
}
261+
262+
// We can ignore any error here, as we are already in the progress
263+
// of shutting down.
264+
let _ = message_send.send(CheckEvent::End);
265+
}))
266+
}
210267
}
211268

212269
#[derive(Debug)]
@@ -215,19 +272,6 @@ pub struct DiagnosticWithFixes {
215272
fixes: Vec<CodeAction>,
216273
}
217274

218-
/// WatchThread exists to wrap around the communication needed to be able to
219-
/// run `cargo check` without blocking. Currently the Rust standard library
220-
/// doesn't provide a way to read sub-process output without blocking, so we
221-
/// have to wrap sub-processes output handling in a thread and pass messages
222-
/// back over a channel.
223-
/// The correct way to dispose of the thread is to drop it, on which the
224-
/// sub-process will be killed, and the thread will be joined.
225-
struct WatchThread {
226-
// XXX: drop order is significant
227-
message_recv: Receiver<CheckEvent>,
228-
_handle: Option<jod_thread::JoinHandle<()>>,
229-
}
230-
231275
enum CheckEvent {
232276
Begin,
233277
Msg(cargo_metadata::Message),
@@ -317,59 +361,3 @@ fn run_cargo(
317361

318362
Err(CargoError(err_msg))
319363
}
320-
321-
impl WatchThread {
322-
fn dummy() -> WatchThread {
323-
WatchThread { message_recv: never(), _handle: None }
324-
}
325-
326-
fn new(options: &CheckConfig, workspace_root: &Path) -> WatchThread {
327-
let mut args: Vec<String> = vec![
328-
options.command.clone(),
329-
"--workspace".to_string(),
330-
"--message-format=json".to_string(),
331-
"--manifest-path".to_string(),
332-
format!("{}/Cargo.toml", workspace_root.display()),
333-
];
334-
if options.all_targets {
335-
args.push("--all-targets".to_string());
336-
}
337-
args.extend(options.args.iter().cloned());
338-
339-
let (message_send, message_recv) = unbounded();
340-
let workspace_root = workspace_root.to_owned();
341-
let handle = if options.enable {
342-
Some(jod_thread::spawn(move || {
343-
// If we trigger an error here, we will do so in the loop instead,
344-
// which will break out of the loop, and continue the shutdown
345-
let _ = message_send.send(CheckEvent::Begin);
346-
347-
let res = run_cargo(&args, Some(&workspace_root), &mut |message| {
348-
// Skip certain kinds of messages to only spend time on what's useful
349-
match &message {
350-
Message::CompilerArtifact(artifact) if artifact.fresh => return true,
351-
Message::BuildScriptExecuted(_) => return true,
352-
Message::Unknown => return true,
353-
_ => {}
354-
}
355-
356-
// if the send channel was closed, we want to shutdown
357-
message_send.send(CheckEvent::Msg(message)).is_ok()
358-
});
359-
360-
if let Err(err) = res {
361-
// FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>`
362-
// to display user-caused misconfiguration errors instead of just logging them here
363-
log::error!("Cargo watcher failed {:?}", err);
364-
}
365-
366-
// We can ignore any error here, as we are already in the progress
367-
// of shutting down.
368-
let _ = message_send.send(CheckEvent::End);
369-
}))
370-
} else {
371-
None
372-
};
373-
WatchThread { message_recv, _handle: handle }
374-
}
375-
}

0 commit comments

Comments
 (0)