8000 perf(language_server): use `Arc<RwLock>` instead of `Mutex` for works… · oxc-project/oxc@0ed6c1a · GitHub
[go: up one dir, main page]

Skip to content

Commit 0ed6c1a

Browse files
committed
perf(language_server): use Arc<RwLock> instead of Mutex for workspace workers (#11328)
`Mutex` will block others requests. `RwLock` + `Arc` is faster. I asked ChatGPT about it: 🔒 So the Real Options Are: ✅ Arc<Mutex<Vec<WorkspaceWorker>>> Use this if: All accesses (read and write) are relatively balanced or infrequent. You want simplicity and don't expect much read contention. ✅ Arc<RwLock<Vec<WorkspaceWorker>>> Use this if: Most access is reading, and you want to allow multiple concurrent reads. Writes (e.g. adding/removing workers) are infrequent. In this case, RwLock may improve performance under heavy read concurrency (e.g., lots of requests querying the current workspace list).
1 parent c4f64aa commit 0ed6c1a

File tree

1 file changed

+19
-16
lines changed
  • crates/oxc_language_server/src

1 file changed

+19
-16
lines changed

crates/oxc_language_server/src/main.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use log::{debug, info, warn};
33
use options::{Options, Run, WorkspaceOption};
44
use rustc_hash::FxBuildHasher;
55
use serde_json::json;
6-
use std::str::FromStr;
7-
use tokio::sync::{Mutex, OnceCell, SetError};
6+
use std::{str::FromStr, sync::Arc};
7+
use tokio::sync::{OnceCell, RwLock, SetError};
88
use tower_lsp_server::{
99
Client, LanguageServer, LspService, Server,
1010
jsonrpc::{Error, ErrorCode, Result},
@@ -42,7 +42,10 @@ struct Backend {
4242
// We must respect each program inside with its own root folder
4343
// and can not use shared programmes across multiple workspaces.
4444
// Each Workspace can have its own server configuration and program root configuration.
45-
workspace_workers: Mutex<Vec<WorkspaceWorker>>,
45+
// WorkspaceWorkers are only written on 2 occasions:
46+
// 1. `initialize` request with workspace folders
47+
// 2. `workspace/didChangeWorkspaceFolders` request
48+
workspace_workers: Arc<RwLock<Vec<WorkspaceWorker>>>,
4649
capabilities: OnceCell<Capabilities>,
4750
}
4851

@@ -115,7 +118,7 @@ impl LanguageServer for Backend {
115118
}
116119
}
117120

118-
*self.workspace_workers.lock().await = workers;
121+
*self.workspace_workers.write().await = workers;
119122

120123
self.capabilities.set(capabilities.clone()).map_err(|err| {
121124
let message = match err {
@@ -144,7 +147,7 @@ impl LanguageServer for Backend {
144147
return;
145148
};
146149

147-
let workers = &*self.workspace_workers.lock().await;
150+
let workers = &*self.workspace_workers.read().await;
148151
let needed_configurations =
149152
ConcurrentHashMap::with_capacity_and_hasher(workers.len(), FxBuildHasher);
150153
let needed_configurations = needed_configurations.pin_owned();
@@ -200,7 +203,7 @@ impl LanguageServer for Backend {
200203
}
201204

202205
async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
203-
let workers = self.workspace_workers.lock().await;
206+
let workers = self.workspace_workers.read().await;
204207
let new_diagnostics: papaya::HashMap<String, Vec<Diagnostic>, FxBuildHasher> =
205208
ConcurrentHashMap::default();
206209
let mut removing_registrations = vec![];
@@ -340,7 +343,7 @@ impl LanguageServer for Backend {
340343
}
341344

342345
async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
343-
let workers = self.workspace_workers.lock().await;
346+
let workers = self.workspace_workers.read().await;
344347
// ToDo: what if an empty changes flag is passed?
345348
debug!("watched file did change");
346349
let all_diagnostics: papaya::HashMap<String, Vec<Diagnostic>, FxBuildHasher> =
@@ -379,7 +382,7 @@ impl LanguageServer for Backend {
379382
}
380383

381384
async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
382-
let mut workers = self.workspace_workers.lock().await;
385+
let mut workers = self.workspace_workers.write().await;
383386
let mut cleared_diagnostics = vec![];
384387
let mut added_registrations = vec![];
385388
let mut removed_registrations = vec![];
@@ -454,7 +457,7 @@ impl LanguageServer for Backend {
454457
async fn did_save(&self, params: DidSaveTextDocumentParams) {
455458
debug!("oxc server did save");
456459
let uri = &params.text_document.uri;
457-
let workers = self.workspace_workers.lock().await;
460+
let workers = self.workspace_workers.read().await;
458461
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
459462
return;
460463
};
@@ -476,7 +479,7 @@ impl LanguageServer for Backend {
476479
/// get the file context from the language client
477480
async fn did_change(&self, params: DidChangeTextDocumentParams) {
478481
let uri = &params.text_document.uri;
479-
let workers = self.workspace_workers.lock().await;
482+
let workers = self.workspace_workers.read().await;
480483
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
481484
return;
482485
};
@@ -497,7 +500,7 @@ impl LanguageServer for Backend {
497500

498501
async fn did_open(&self, params: DidOpenTextDocumentParams) {
499502
let uri = &params.text_document.uri;
500-
let workers = self.workspace_workers.lock().await;
503+
let workers = self.workspace_workers.read().await;
501504
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
502505
return;
503506
};
@@ -516,7 +519,7 @@ impl LanguageServer for Backend {
516519

517520
async fn did_close(&self, params: DidCloseTextDocumentParams) {
518521
let uri = &params.text_document.uri;
519-
let workers = self.workspace_workers.lock().await;
522+
let workers = self.workspace_workers.read().await;
520523
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
521524
return;
522525
};
@@ -525,7 +528,7 @@ impl LanguageServer for Backend {
525528

526529
async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
527530
let uri = &params.text_document.uri;
528-
let workers = self.workspace_workers.lock().await;
531+
let workers = self.workspace_workers.read().await;
529532
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
530533
return Ok(None);
531534
};
@@ -558,7 +561,7 @@ impl LanguageServer for Backend {
558561
FixAllCommandArgs::try_from(params.arguments).map_err(Error::invalid_params)?;
559562

560563
let uri = &Uri::from_str(&args.uri).unwrap();
561-
let workers = self.workspace_workers.lock().await;
564+
let workers = self.workspace_workers.read().await;
562565
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri))
563566
else {
564567
return Ok(None);
@@ -618,7 +621,7 @@ impl Backend {
618621
// clears all diagnostics for workspace folders
619622
async fn clear_all_diagnostics(&self) {
620623
let mut cleared_diagnostics = vec![];
621-
for worker in self.workspace_workers.lock().await.iter() {
624+
for worker in self.workspace_workers.read().await.iter() {
622625
cleared_diagnostics.extend(worker.get_clear_diagnostics());
623626
}
624627
self.publish_all_diagnostics(&cleared_diagnostics).await;
@@ -641,7 +644,7 @@ async fn main() {
641644

642645
let (service, socket) = LspService::build(|client| Backend {
643646
client,
644-
workspace_workers: Mutex::new(vec![]),
647+
workspace_workers: Arc::new(RwLock::new(vec![])),
645648
capabilities: OnceCell::new(),
646649
})
647650
.finish();

0 commit comments

Comments
 (0)
0