Skip to content

Commit cbe7721

Browse files
TysonAndreautozimu
authored andcommitted
Add a clients_mutex for starting new language servers
This prevents more than one language server (per languageId) from starting up at any point in time. Fixes #878
1 parent 4fd272b commit cbe7721

File tree

3 files changed

+58
-4
lines changed

3 files changed

+58
-4
lines changed

src/language_client.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,42 @@ use super::*;
22
use crate::vim::Vim;
33
use std::ops::DerefMut;
44

5-
pub struct LanguageClient(pub Arc<Mutex<State>>);
5+
pub struct LanguageClient {
6+
pub state_mutex: Arc<Mutex<State>>,
7+
pub clients_mutex: Arc<Mutex<HashMap<LanguageId, Arc<Mutex<()>>>>>,
8+
}
69

710
impl LanguageClient {
811
// NOTE: Don't expose this as public.
912
// MutexGuard could easily halt the program when one guard is not released immediately after use.
1013
fn lock(&self) -> Fallible<MutexGuard<State>> {
11-
self.0
14+
self.state_mutex
1215
.lock()
1316
.map_err(|err| format_err!("Failed to lock state: {:?}", err))
1417
}
1518

19+
// This fetches a mutex that is unique to the provided languageId.
20+
//
21+
// Here, we return a mutex instead of the mutex guard because we need to satisfy the borrow
22+
// checker. Otherwise, there is no way to guarantee that the mutex in the hash map wouldn't be
23+
// garbage collected as a result of another modification updating the hash map, while something was holding the lock
24+
pub fn get_client_update_mutex(&self, languageId: LanguageId) -> Fallible<Arc<Mutex<()>>> {
25+
let map_guard = self.clients_mutex.lock();
26+
if map_guard.is_err() {
27+
return Err(format_err!(
28+
"Failed to lock client creation for languageId {:?}: {:?}",
29+
languageId,
30+
map_guard.unwrap_err()
31+
));
32+
}
33+
let mut map = map_guard.unwrap();
34+
if !map.contains_key(&languageId) {
35+
map.insert(languageId.clone(), Arc::new(Mutex::new(())));
36+
}
37+
let mutex: Arc<Mutex<()>> = map.get(&languageId).unwrap().clone();
38+
Ok(mutex)
39+
}
40+
1641
pub fn get<T>(&self, f: impl FnOnce(&State) -> T) -> Fallible<T> {
1742
Ok(f(self.lock()?.deref()))
1843
}

src/language_server_protocol.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ impl LanguageClient {
2525

2626
pub fn loop_call(&self, rx: &crossbeam_channel::Receiver<Call>) -> Fallible<()> {
2727
for call in rx.iter() {
28-
let language_client = Self(self.0.clone());
28+
let language_client = LanguageClient {
29+
state_mutex: self.state_mutex.clone(),
30+
clients_mutex: self.clients_mutex.clone(), // not sure if useful to clone this
31+
};
2932
thread::spawn(move || {
3033
if let Err(err) = language_client.handle_call(call) {
3134
error!("Error handling request:\n{:?}", err);
@@ -2827,6 +2830,29 @@ impl LanguageClient {
28272830
let cmdparams = vim_cmd_args_to_value(&cmdargs)?;
28282831
let params = params.combine(&cmdparams);
28292832

2833+
// When multiple buffers get opened up concurrently,
2834+
// startServer gets called concurrently.
2835+
// This lock ensures that at most one language server is starting up at a time per
2836+
// languageId.
2837+
// We keep the mutex in scope to satisfy the borrow checker.
2838+
// This ensures that the mutex isn't garbage collected while the MutexGuard is held.
2839+
//
2840+
// - e.g. prevents starting multiple servers with `vim -p`.
2841+
// - This continues to allow distinct language servers to start up concurrently
2842+
// by languageId (e.g. java and rust)
2843+
// - Revisit this when more than one server is allowed per languageId.
2844+
// (ensure that the mutex is acquired by what starts the group of servers)
2845+
//
2846+
// TODO: May want to lock other methods that update the list of clients.
2847+
let mutex_for_language_id = self.get_client_update_mutex(Some(languageId.clone()))?;
2848+
let _raii_lock: MutexGuard<()> = mutex_for_language_id.lock().map_err(|err| {
2849+
format_err!(
2850+
"Failed to lock client creation for languageId {:?}: {:?}",
2851+
languageId,
2852+
err
2853+
)
2854+
})?;
2855+
28302856
if self.get(|state| state.clients.contains_key(&Some(languageId.clone())))? {
28312857
return Ok(json!({}));
28322858
}

src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ fn main() -> Fallible<()> {
6161
let _ = args.get_matches();
6262

6363
let (tx, rx) = crossbeam_channel::unbounded();
64-
let language_client = language_client::LanguageClient(Arc::new(Mutex::new(State::new(tx)?)));
64+
let language_client = language_client::LanguageClient {
65+
state_mutex: Arc::new(Mutex::new(State::new(tx)?)),
66+
clients_mutex: Arc::new(Mutex::new(HashMap::new())),
67+
};
6568

6669
language_client.loop_call(&rx)
6770
}

0 commit comments

Comments
 (0)