Skip to content

Commit afb5c62

Browse files
committed
automatically wait for worker threads
closes #817
1 parent b85c189 commit afb5c62

File tree

7 files changed

+128
-138
lines changed

7 files changed

+128
-138
lines changed

Cargo.lock

-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ra_lsp_server/src/main_loop.rs

+12-14
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,20 @@ pub fn main_loop(
5454
) -> Result<()> {
5555
let pool = ThreadPool::new(THREADPOOL_SIZE);
5656
let (task_sender, task_receiver) = unbounded::<Task>();
57-
let (ws_worker, ws_watcher) = workspace_loader();
5857

59-
ws_worker.send(ws_root.clone()).unwrap();
6058
// FIXME: support dynamic workspace loading.
61-
let workspaces = match ws_worker.recv().unwrap() {
62-
Ok(ws) => vec![ws],
63-
Err(e) => {
64-
log::error!("loading workspace failed: {}", e);
65-
Vec::new()
59+
let workspaces = {
60+
let ws_worker = workspace_loader();
61+
ws_worker.sender().send(ws_root.clone()).unwrap();
62+
match ws_worker.receiver().recv().unwrap() {
63+
Ok(ws) => vec![ws],
64+
Err(e) => {
65+
log::error!("loading workspace failed: {}", e);
66+
Vec::new()
67+
}
6668
}
6769
};
68-
ws_worker.shutdown();
69-
ws_watcher.shutdown().map_err(|_| format_err!("ws watcher died"))?;
70+
7071
let mut state = ServerWorldState::new(ws_root.clone(), workspaces);
7172

7273
log::info!("server initialized, serving requests");
@@ -94,12 +95,9 @@ pub fn main_loop(
9495
log::info!("...threadpool has finished");
9596

9697
let vfs = Arc::try_unwrap(state.vfs).expect("all snapshots should be dead");
97-
let vfs_res = vfs.into_inner().shutdown();
98+
drop(vfs);
9899

99-
main_res?;
100-
vfs_res.map_err(|_| format_err!("fs watcher died"))?;
101-
102-
Ok(())
100+
main_res
103101
}
104102

105103
enum Event {

crates/ra_lsp_server/src/project_model.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
use std::path::PathBuf;
22

3-
use thread_worker::{WorkerHandle, Worker};
3+
use thread_worker::Worker;
44

55
use crate::Result;
66

77
pub use ra_project_model::{
88
ProjectWorkspace, CargoWorkspace, Package, Target, TargetKind, Sysroot,
99
};
1010

11-
pub fn workspace_loader() -> (Worker<PathBuf, Result<ProjectWorkspace>>, WorkerHandle) {
12-
thread_worker::spawn::<PathBuf, Result<ProjectWorkspace>, _>(
11+
pub fn workspace_loader() -> Worker<PathBuf, Result<ProjectWorkspace>> {
12+
Worker::<PathBuf, Result<ProjectWorkspace>>::spawn(
1313
"workspace loader",
1414
1,
1515
|input_receiver, output_sender| {

crates/ra_vfs/src/io.rs

+50-56
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
use std::{
22
fs,
3-
thread,
43
path::{Path, PathBuf},
54
sync::{mpsc, Arc},
65
time::Duration,
76
};
87
use crossbeam_channel::{Receiver, Sender, unbounded, RecvError, select};
98
use relative_path::RelativePathBuf;
10-
use thread_worker::WorkerHandle;
119
use walkdir::WalkDir;
1210
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
1311

@@ -49,8 +47,7 @@ enum ChangeKind {
4947
const WATCHER_DELAY: Duration = Duration::from_millis(250);
5048

5149
pub(crate) struct Worker {
52-
worker: thread_worker::Worker<Task, TaskResult>,
53-
worker_handle: WorkerHandle,
50+
thread_worker: thread_worker::Worker<Task, TaskResult>,
5451
}
5552

5653
impl Worker {
@@ -62,82 +59,79 @@ impl Worker {
6259
// * we want to read all files from a single thread, to guarantee that
6360
// we always get fresher versions and never go back in time.
6461
// * we want to tear down everything neatly during shutdown.
65-
let (worker, worker_handle) = thread_worker::spawn(
62+
let thread_worker = thread_worker::Worker::spawn(
6663
"vfs",
6764
128,
6865
// This are the channels we use to communicate with outside world.
6966
// If `input_receiver` is closed we need to tear ourselves down.
7067
// `output_sender` should not be closed unless the parent died.
7168
move |input_receiver, output_sender| {
72-
// These are `std` channels notify will send events to
73-
let (notify_sender, notify_receiver) = mpsc::channel();
69+
// Make sure that the destruction order is
70+
//
71+
// * input channel
72+
// * thread
73+
// * output channel
74+
//
75+
// this is required to avoid deadlocks.
76+
7477
// These are the corresponding crossbeam channels
7578
let (watcher_sender, watcher_receiver) = unbounded();
79+
let _thread;
80+
{
81+
// These are `std` channels notify will send events to
82+
let (notify_sender, notify_receiver) = mpsc::channel();
7683

77-
let mut watcher = notify::watcher(notify_sender, WATCHER_DELAY)
78-
.map_err(|e| log::error!("failed to spawn notify {}", e))
79-
.ok();
80-
// Start a silly thread to transform between two channels
81-
let thread = thread::spawn(move || {
82-
notify_receiver
83-
.into_iter()
84-
.for_each(|event| convert_notify_event(event, &watcher_sender))
85-
});
84+
let mut watcher = notify::watcher(notify_sender, WATCHER_DELAY)
85+
.map_err(|e| log::error!("failed to spawn notify {}", e))
86+
.ok();
87+
// Start a silly thread to transform between two channels
88+
_thread = thread_worker::ScopedThread::spawn("notify-convertor", move || {
89+
notify_receiver
90+
.into_iter()
91+
.for_each(|event| convert_notify_event(event, &watcher_sender))
92+
});
8693

87-
// Process requests from the called or notifications from
88-
// watcher until the caller says stop.
89-
loop {
90-
select! {
91-
// Received request from the caller. If this channel is
92-
// closed, we should shutdown everything.
93-
recv(input_receiver) -> t => match t {
94-
Err(RecvError) => {
95-
drop(input_receiver);
96-
break
94+
// Process requests from the called or notifications from
95+
// watcher until the caller says stop.
96+
loop {
97+
select! {
98+
// Received request from the caller. If this channel is
99+
// closed, we should shutdown everything.
100+
recv(input_receiver) -> t => match t {
101+
Err(RecvError) => {
102+
drop(input_receiver);
103+
break
104+
},
105+
Ok(Task::AddRoot { root, config }) => {
106+
watch_root(watcher.as_mut(), &output_sender, root, Arc::clone(&config));
107+
}
108+
},
109+
// Watcher send us changes. If **this** channel is
110+
// closed, the watcher has died, which indicates a bug
111+
// -- escalate!
112+
recv(watcher_receiver) -> event => match event {
113+
Err(RecvError) => panic!("watcher is dead"),
114+
Ok((path, change)) => {
115+
handle_change(watcher.as_mut(), &output_sender, &*roots, path, change);
116+
}
97117
},
98-
Ok(Task::AddRoot { root, config }) => {
99-
watch_root(watcher.as_mut(), &output_sender, root, Arc::clone(&config));
100-
}
101-
},
102-
// Watcher send us changes. If **this** channel is
103-
// closed, the watcher has died, which indicates a bug
104-
// -- escalate!
105-
recv(watcher_receiver) -> event => match event {
106-
Err(RecvError) => panic!("watcher is dead"),
107-
Ok((path, change)) => {
108-
handle_change(watcher.as_mut(), &output_sender, &*roots, path, change);
109-
}
110-
},
118+
}
111119
}
112120
}
113-
// Stopped the watcher
114-
drop(watcher.take());
115121
// Drain pending events: we are not interested in them anyways!
116122
watcher_receiver.into_iter().for_each(|_| ());
117-
118-
let res = thread.join();
119-
match &res {
120-
Ok(()) => log::info!("... Watcher terminated with ok"),
121-
Err(_) => log::error!("... Watcher terminated with err"),
122-
}
123-
res.unwrap();
124123
},
125124
);
126125

127-
Worker { worker, worker_handle }
126+
Worker { thread_worker }
128127
}
129128

130129
pub(crate) fn sender(&self) -> &Sender<Task> {
131-
&self.worker.inp
130+
&self.thread_worker.sender()
132131
}
133132

134133
pub(crate) fn receiver(&self) -> &Receiver<TaskResult> {
135-
&self.worker.out
136-
}
137-
138-
pub(crate) fn shutdown(self) -> thread::Result<()> {
139-
let _ = self.worker.shutdown();
140-
self.worker_handle.shutdown()
134+
&self.thread_worker.receiver()
141135
}
142136
}
143137

crates/ra_vfs/src/lib.rs

-6
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ use std::{
2222
fmt, fs, mem,
2323
path::{Path, PathBuf},
2424
sync::Arc,
25-
thread,
2625
};
2726

2827
use crossbeam_channel::Receiver;
@@ -337,11 +336,6 @@ impl Vfs {
337336
mem::replace(&mut self.pending_changes, Vec::new())
338337
}
339338

340-
/// Shutdown the VFS and terminate the background watching thread.
341-
pub fn shutdown(self) -> thread::Result<()> {
342-
self.worker.shutdown()
343-
}
344-
345339
fn add_file(
346340
&mut self,
347341
root: VfsRoot,

crates/thread_worker/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ version = "0.1.0"
55
authors = ["rust-analyzer developers"]
66

77
[dependencies]
8-
drop_bomb = "0.1.0"
98
crossbeam-channel = "0.3.5"
109
log = "0.4.3"
1110

crates/thread_worker/src/lib.rs

+63-57
Original file line numberDiff line numberDiff line change
@@ -2,74 +2,80 @@
22
33
use std::thread;
44

5-
use crossbeam_channel::{bounded, unbounded, Receiver, Sender, RecvError, SendError};
6-
use drop_bomb::DropBomb;
5+
use crossbeam_channel::{bounded, unbounded, Receiver, Sender};
76

8-
pub struct Worker<I, O> {
9-
pub inp: Sender<I>,
10-
pub out: Receiver<O>,
7+
/// Like `std::thread::JoinHandle<()>`, but joins thread in drop automatically.
8+
pub struct ScopedThread {
9+
// Option for drop
10+
inner: Option<thread::JoinHandle<()>>,
1111
}
1212

13-
pub struct WorkerHandle {
14-
name: &'static str,
15-
thread: thread::JoinHandle<()>,
16-
bomb: DropBomb,
17-
}
13+
impl Drop for ScopedThread {
14+
fn drop(&mut self) {
15+
let inner = self.inner.take().unwrap();
16+
let name = inner.thread().name().unwrap().to_string();
17+
log::info!("waiting for {} to finish...", name);
18+
let res = inner.join();
19+
log::info!(".. {} terminated with {}", name, if res.is_ok() { "ok" } else { "err" });
1820

19-
pub fn spawn<I, O, F>(name: &'static str, buf: usize, f: F) -> (Worker<I, O>, WorkerHandle)
20-
where
21-
F: FnOnce(Receiver<I>, Sender<O>) + Send + 'static,
22-
I: Send + 'static,
23-
O: Send + 'static,
24-
{
25-
let (worker, inp_r, out_s) = worker_chan(buf);
26-
let watcher = WorkerHandle::spawn(name, move || f(inp_r, out_s));
27-
(worker, watcher)
28-
}
29-
30-
impl<I, O> Worker<I, O> {
31-
/// Stops the worker. Returns the message receiver to fetch results which
32-
/// have become ready before the worker is stopped.
33-
pub fn shutdown(self) -> Receiver<O> {
34-
self.out
21+
// escalate panic, but avoid aborting the process
22+
match res {
23+
Err(e) => {
24+
if !thread::panicking() {
25+
panic!(e)
26+
}
27+
}
28+
_ => (),
29+
}
3530
}
31+
}
3632

37-
pub fn send(&self, item: I) -> Result<(), SendError<I>> {
38-
self.inp.send(item)
39-
}
40-
pub fn recv(&self) -> Result<O, RecvError> {
41-
self.out.recv()
33+
impl ScopedThread {
34+
pub fn spawn(name: &'static str, f: impl FnOnce() + Send + 'static) -> ScopedThread {
35+
let inner = thread::Builder::new().name(name.into()).spawn(f).unwrap();
36+
ScopedThread { inner: Some(inner) }
4237
}
4338
}
4439

45-
impl WorkerHandle {
46-
fn spawn(name: &'static str, f: impl FnOnce() + Send + 'static) -> WorkerHandle {
47-
let thread = thread::spawn(f);
48-
WorkerHandle {
49-
name,
50-
thread,
51-
bomb: DropBomb::new(format!("WorkerHandle {} was not shutdown", name)),
52-
}
53-
}
40+
/// A wrapper around event-processing thread with automatic shutdown semantics.
41+
pub struct Worker<I, O> {
42+
// XXX: field order is significant here.
43+
//
44+
// In Rust, fields are dropped in the declaration order, and we rely on this
45+
// here. We must close input first, so that the `thread` (who holds the
46+
// opposite side of the channel) noticed shutdown. Then, we must join the
47+
// thread, but we must keep out alive so that the thread does not panic.
48+
//
49+
// Note that a potential problem here is that we might drop some messages
50+
// from receiver on the floor. This is ok for rust-analyzer: we have only a
51+
// single client, so, if we are shutting down, nobody is interested in the
52+
// unfinished work anyway!
53+
sender: Sender<I>,
54+
_thread: ScopedThread,
55+
receiver: Receiver<O>,
56+
}
5457

55-
pub fn shutdown(mut self) -> thread::Result<()> {
56-
log::info!("waiting for {} to finish ...", self.name);
57-
let name = self.name;
58-
self.bomb.defuse();
59-
let res = self.thread.join();
60-
match &res {
61-
Ok(()) => log::info!("... {} terminated with ok", name),
62-
Err(_) => log::error!("... {} terminated with err", name),
63-
}
64-
res
58+
impl<I, O> Worker<I, O> {
59+
pub fn spawn<F>(name: &'static str, buf: usize, f: F) -> Worker<I, O>
60+
where
61+
F: FnOnce(Receiver<I>, Sender<O>) + Send + 'static,
62+
I: Send + 'static,
63+
O: Send + 'static,
64+
{
65+
// Set up worker channels in a deadlock-avoiding way. If one sets both input
66+
// and output buffers to a fixed size, a worker might get stuck.
67+
let (sender, input_receiver) = bounded::<I>(buf);
68+
let (output_sender, receiver) = unbounded::<O>();
69+
let _thread = ScopedThread::spawn(name, move || f(input_receiver, output_sender));
70+
Worker { sender, _thread, receiver }
6571
}
6672
}
6773

68-
/// Sets up worker channels in a deadlock-avoiding way.
69-
/// If one sets both input and output buffers to a fixed size,
70-
/// a worker might get stuck.
71-
fn worker_chan<I, O>(buf: usize) -> (Worker<I, O>, Receiver<I>, Sender<O>) {
72-
let (input_sender, input_receiver) = bounded::<I>(buf);
73-
let (output_sender, output_receiver) = unbounded::<O>();
74-
(Worker { inp: input_sender, out: output_receiver }, input_receiver, output_sender)
74+
impl<I, O> Worker<I, O> {
75+
pub fn sender(&self) -> &Sender<I> {
76+
&self.sender
77+
}
78+
pub fn receiver(&self) -> &Receiver<O> {
79+
&self.receiver
80+
}
7581
}

0 commit comments

Comments
 (0)