1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-03 04:48:52 -05:00

refactor(lsp): remove RwLock on Config (#13485)

This commit is contained in:
David Sherret 2022-01-25 10:30:38 -05:00 committed by GitHub
parent 0e12acc6ff
commit d4dd9ae4cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 274 additions and 202 deletions

View file

@ -3,15 +3,19 @@ use std::pin::Pin;
use std::sync::Arc;
use deno_core::anyhow::anyhow;
use deno_core::anyhow::bail;
use deno_core::error::AnyError;
use deno_core::futures::future;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use lspower::lsp;
use lspower::lsp::ConfigurationItem;
use crate::lsp::config::SETTINGS_SECTION;
use crate::lsp::repl::get_repl_workspace_settings;
use super::config::SpecifierSettings;
use super::config::SETTINGS_SECTION;
use super::lsp_custom;
#[derive(Clone)]
@ -48,11 +52,39 @@ impl Client {
self.0.send_registry_state_notification(params).await;
}
pub async fn configuration(
pub async fn specifier_configurations(
&self,
items: Vec<lsp::ConfigurationItem>,
) -> Result<Vec<serde_json::Value>, AnyError> {
self.0.configuration(items).await
specifiers: Vec<lsp::Url>,
) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> {
self.0.specifier_configurations(specifiers).await
}
pub async fn specifier_configuration(
&self,
specifier: &lsp::Url,
) -> Result<SpecifierSettings, AnyError> {
let values = self
.0
.specifier_configurations(vec![specifier.clone()])
.await?;
if let Some(value) = values.into_iter().next() {
value.map_err(|err| {
anyhow!(
"Error converting specifier settings ({}): {}",
specifier,
err
)
})
} else {
bail!(
"Expected the client to return a configuration item for specifier: {}",
specifier
);
}
}
pub async fn workspace_configuration(&self) -> Result<Value, AnyError> {
self.0.workspace_configuration().await
}
pub async fn show_message(
@ -87,10 +119,11 @@ trait ClientTrait: Send + Sync {
&self,
params: lsp_custom::RegistryStateNotificationParams,
) -> AsyncReturn<()>;
fn configuration(
fn specifier_configurations(
&self,
items: Vec<lsp::ConfigurationItem>,
) -> AsyncReturn<Result<Vec<serde_json::Value>, AnyError>>;
uris: Vec<lsp::Url>,
) -> AsyncReturn<Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>>;
fn workspace_configuration(&self) -> AsyncReturn<Result<Value, AnyError>>;
fn show_message(
&self,
message_type: lsp::MessageType,
@ -132,16 +165,56 @@ impl ClientTrait for LspowerClient {
})
}
fn configuration(
fn specifier_configurations(
&self,
items: Vec<lsp::ConfigurationItem>,
) -> AsyncReturn<Result<Vec<serde_json::Value>, AnyError>> {
uris: Vec<lsp::Url>,
) -> AsyncReturn<Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>>
{
let client = self.0.clone();
Box::pin(async move {
client
.configuration(items)
.await
.map_err(|err| anyhow!("{}", err))
let config_response = client
.configuration(
uris
.into_iter()
.map(|uri| ConfigurationItem {
scope_uri: Some(uri),
section: Some(SETTINGS_SECTION.to_string()),
})
.collect(),
)
.await?;
Ok(
config_response
.into_iter()
.map(|value| {
serde_json::from_value::<SpecifierSettings>(value).map_err(|err| {
anyhow!("Error converting specifier settings: {}", err)
})
})
.collect(),
)
})
}
fn workspace_configuration(&self) -> AsyncReturn<Result<Value, AnyError>> {
let client = self.0.clone();
Box::pin(async move {
let config_response = client
.configuration(vec![ConfigurationItem {
scope_uri: None,
section: Some(SETTINGS_SECTION.to_string()),
}])
.await;
match config_response {
Ok(value_vec) => match value_vec.get(0).cloned() {
Some(value) => Ok(value),
None => bail!("Missing response workspace configuration."),
},
Err(err) => {
bail!("Error getting workspace configuration: {}", err)
}
}
})
}
@ -188,27 +261,28 @@ impl ClientTrait for ReplClient {
Box::pin(future::ready(()))
}
fn configuration(
fn specifier_configurations(
&self,
items: Vec<lsp::ConfigurationItem>,
) -> AsyncReturn<Result<Vec<serde_json::Value>, AnyError>> {
let is_global_config_request = items.len() == 1
&& items[0].scope_uri.is_none()
&& items[0].section.as_deref() == Some(SETTINGS_SECTION);
let response = if is_global_config_request {
vec![serde_json::to_value(get_repl_workspace_settings()).unwrap()]
} else {
// all specifiers are enabled for the REPL
items
.into_iter()
.map(|_| {
json!({
"enable": true,
})
uris: Vec<lsp::Url>,
) -> AsyncReturn<Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>>
{
// all specifiers are enabled for the REPL
let settings = uris
.into_iter()
.map(|_| {
Ok(SpecifierSettings {
enable: true,
..Default::default()
})
.collect()
};
Box::pin(future::ready(Ok(response)))
})
.collect();
Box::pin(future::ready(Ok(settings)))
}
fn workspace_configuration(&self) -> AsyncReturn<Result<Value, AnyError>> {
Box::pin(future::ready(Ok(
serde_json::to_value(get_repl_workspace_settings()).unwrap(),
)))
}
fn show_message(

View file

@ -226,6 +226,12 @@ enum ConfigRequest {
Specifier(ModuleSpecifier, ModuleSpecifier),
}
#[derive(Debug, Clone)]
pub struct SpecifierWithClientUri {
pub specifier: ModuleSpecifier,
pub client_uri: ModuleSpecifier,
}
#[derive(Debug, Default, Clone)]
pub struct Settings {
pub specifiers:
@ -236,149 +242,62 @@ pub struct Settings {
#[derive(Debug)]
pub struct Config {
pub client_capabilities: ClientCapabilities,
settings: Arc<RwLock<Settings>>,
tx: mpsc::Sender<ConfigRequest>,
settings: Settings,
pub workspace_folders: Option<Vec<WorkspaceFolder>>,
}
impl Config {
pub fn new(client: Client) -> Self {
let (tx, mut rx) = mpsc::channel::<ConfigRequest>(100);
let settings = Arc::new(RwLock::new(Settings::default()));
let settings_ref = settings.clone();
let _join_handle = thread::spawn(move || {
let runtime = create_basic_runtime();
runtime.block_on(async {
loop {
match rx.recv().await {
None => break,
Some(ConfigRequest::All) => {
let (specifier_uri_map, items): (
Vec<(ModuleSpecifier, ModuleSpecifier)>,
Vec<lsp::ConfigurationItem>,
) = {
let settings = settings_ref.read();
(
settings
.specifiers
.iter()
.map(|(s, (u, _))| (s.clone(), u.clone()))
.collect(),
settings
.specifiers
.iter()
.map(|(_, (uri, _))| lsp::ConfigurationItem {
scope_uri: Some(uri.clone()),
section: Some(SETTINGS_SECTION.to_string()),
})
.collect(),
)
};
if let Ok(configs) = client.configuration(items).await {
let mut settings = settings_ref.write();
for (i, value) in configs.into_iter().enumerate() {
match serde_json::from_value::<SpecifierSettings>(value) {
Ok(specifier_settings) => {
let (specifier, uri) = specifier_uri_map[i].clone();
settings
.specifiers
.insert(specifier, (uri, specifier_settings));
}
Err(err) => {
error!("Error converting specifier settings: {}", err);
}
}
}
}
}
Some(ConfigRequest::Specifier(specifier, uri)) => {
if settings_ref.read().specifiers.contains_key(&specifier) {
continue;
}
match client
.configuration(vec![lsp::ConfigurationItem {
scope_uri: Some(uri.clone()),
section: Some(SETTINGS_SECTION.to_string()),
}])
.await
{
Ok(values) => {
if let Some(value) = values.first() {
match serde_json::from_value::<SpecifierSettings>(value.clone()) {
Ok(specifier_settings) => {
settings_ref
.write()
.specifiers
.insert(specifier, (uri, specifier_settings));
}
Err(err) => {
error!("Error converting specifier settings ({}): {}", specifier, err);
}
}
} else {
error!("Expected the client to return a configuration item for specifier: {}", specifier);
}
},
Err(err) => {
error!(
"Error retrieving settings for specifier ({}): {}",
specifier,
err,
);
}
}
}
}
}
})
});
pub fn new() -> Self {
Self {
client_capabilities: ClientCapabilities::default(),
settings,
tx,
settings: Default::default(),
workspace_folders: None,
}
}
pub fn get_workspace_settings(&self) -> WorkspaceSettings {
self.settings.read().workspace.clone()
self.settings.workspace.clone()
}
/// Set the workspace settings directly, which occurs during initialization
/// and when the client does not support workspace configuration requests
pub fn set_workspace_settings(&self, value: Value) -> Result<(), AnyError> {
pub fn set_workspace_settings(
&mut self,
value: Value,
) -> Result<(), AnyError> {
let workspace_settings = serde_json::from_value(value)?;
self.settings.write().workspace = workspace_settings;
self.settings.workspace = workspace_settings;
Ok(())
}
pub fn snapshot(&self) -> Arc<ConfigSnapshot> {
Arc::new(ConfigSnapshot {
client_capabilities: self.client_capabilities.clone(),
settings: self.settings.read().clone(),
settings: self.settings.clone(),
workspace_folders: self.workspace_folders.clone(),
})
}
pub fn has_specifier_settings(&self, specifier: &ModuleSpecifier) -> bool {
self.settings.specifiers.contains_key(specifier)
}
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
let settings = self.settings.read();
settings
self
.settings
.specifiers
.get(specifier)
.map(|(_, s)| s.enable)
.unwrap_or_else(|| settings.workspace.enable)
.unwrap_or_else(|| self.settings.workspace.enable)
}
pub fn specifier_code_lens_test(&self, specifier: &ModuleSpecifier) -> bool {
let settings = self.settings.read();
let value = settings
let value = self
.settings
.specifiers
.get(specifier)
.map(|(_, s)| s.code_lens.test)
.unwrap_or_else(|| settings.workspace.code_lens.test);
.unwrap_or_else(|| self.settings.workspace.code_lens.test);
value
}
@ -416,26 +335,28 @@ impl Config {
}
}
/// Update all currently cached specifier settings
pub async fn update_all_settings(&self) -> Result<(), AnyError> {
pub fn get_specifiers_with_client_uris(&self) -> Vec<SpecifierWithClientUri> {
self
.tx
.send(ConfigRequest::All)
.await
.map_err(|_| anyhow!("Error sending config update task."))
.settings
.specifiers
.iter()
.map(|(s, (u, _))| SpecifierWithClientUri {
specifier: s.clone(),
client_uri: u.clone(),
})
.collect::<Vec<_>>()
}
/// Update a specific specifiers settings from the client.
pub async fn update_specifier_settings(
&self,
specifier: &ModuleSpecifier,
uri: &ModuleSpecifier,
) -> Result<(), AnyError> {
pub fn set_specifier_settings(
&mut self,
specifier: ModuleSpecifier,
client_uri: ModuleSpecifier,
settings: SpecifierSettings,
) {
self
.tx
.send(ConfigRequest::Specifier(specifier.clone(), uri.clone()))
.await
.map_err(|_| anyhow!("Error sending config update task."))
.settings
.specifiers
.insert(specifier, (client_uri, settings));
}
}
@ -466,17 +387,12 @@ mod tests {
}
fn setup() -> Config {
let mut maybe_client: Option<Client> = None;
let (_service, _) = lspower::LspService::new(|client| {
maybe_client = Some(Client::from_lspower(client));
MockLanguageServer::default()
});
Config::new(maybe_client.unwrap())
Config::new()
}
#[test]
fn test_config_specifier_enabled() {
let config = setup();
let mut config = setup();
let specifier = resolve_url("file:///a.ts").unwrap();
assert!(!config.specifier_enabled(&specifier));
config
@ -489,7 +405,7 @@ mod tests {
#[test]
fn test_set_workspace_settings_defaults() {
let config = setup();
let mut config = setup();
config
.set_workspace_settings(json!({}))
.expect("could not update");

View file

@ -148,7 +148,7 @@ impl Inner {
let documents = Documents::new(&location);
let performance = Arc::new(Performance::default());
let ts_server = Arc::new(TsServer::new(performance.clone()));
let config = Config::new(client.clone());
let config = Config::new();
let diagnostics_server = DiagnosticsServer::new(
client.clone(),
performance.clone(),
@ -741,17 +741,12 @@ impl Inner {
Ok(())
}
async fn did_open(&mut self, params: DidOpenTextDocumentParams) {
async fn did_open(
&mut self,
specifier: &ModuleSpecifier,
params: DidOpenTextDocumentParams,
) {
let mark = self.performance.mark("did_open", Some(&params));
let specifier = self.url_map.normalize_url(&params.text_document.uri);
if let Err(err) = self
.config
.update_specifier_settings(&specifier, &params.text_document.uri)
.await
{
error!("Error updating specifier settings: {}", err);
}
if params.text_document.uri.scheme() == "deno" {
// we can ignore virtual text documents opening, as they don't need to
@ -785,7 +780,7 @@ impl Inner {
if document.is_diagnosable() {
self
.diagnostics_server
.invalidate(self.documents.dependents(&specifier))
.invalidate(self.documents.dependents(specifier))
.await;
if let Err(err) = self.diagnostics_server.update() {
error!("{}", err);
@ -845,31 +840,12 @@ impl Inner {
async fn did_change_configuration(
&mut self,
client_workspace_config: Option<Value>,
params: DidChangeConfigurationParams,
) {
let mark = self
.performance
.mark("did_change_configuration", Some(&params));
let maybe_config =
if self.config.client_capabilities.workspace_configuration {
let config_response = self
.client
.configuration(vec![ConfigurationItem {
scope_uri: None,
section: Some(SETTINGS_SECTION.to_string()),
}])
.await;
if let Err(err) = self.config.update_all_settings().await {
error!("Cannot request updating all settings: {}", err);
}
match config_response {
Ok(value_vec) => value_vec.get(0).cloned(),
Err(err) => {
error!("Error getting workspace configuration: {}", err);
None
}
}
client_workspace_config
} else {
params
.settings
@ -908,8 +884,6 @@ impl Inner {
self.maybe_import_map.clone(),
self.maybe_config_file.as_ref(),
);
self.performance.measure(mark);
}
async fn did_change_watched_files(
@ -2376,7 +2350,39 @@ impl lspower::LanguageServer for LanguageServer {
}
async fn did_open(&self, params: DidOpenTextDocumentParams) {
self.0.lock().await.did_open(params).await
let (client, uri, specifier, had_specifier_settings) = {
let mut inner = self.0.lock().await;
let client = inner.client.clone();
let uri = params.text_document.uri.clone();
let specifier = inner.url_map.normalize_url(&uri);
inner.did_open(&specifier, params).await;
let has_specifier_settings =
inner.config.has_specifier_settings(&specifier);
(client, uri, specifier, has_specifier_settings)
};
// retrieve the specifier settings outside the lock if
// they haven't been asked for yet on its own time
if !had_specifier_settings {
let language_server = self.clone();
tokio::spawn(async move {
let response = client.specifier_configuration(&uri).await;
match response {
Ok(specifier_settings) => {
// now update the config
let mut inner = language_server.0.lock().await;
inner.config.set_specifier_settings(
specifier,
uri,
specifier_settings,
);
}
Err(err) => {
error!("{}", err);
}
}
});
}
}
async fn did_change(&self, params: DidChangeTextDocumentParams) {
@ -2396,7 +2402,83 @@ impl lspower::LanguageServer for LanguageServer {
&self,
params: DidChangeConfigurationParams,
) {
self.0.lock().await.did_change_configuration(params).await
let (has_workspace_capability, client, specifiers, mark) = {
let inner = self.0.lock().await;
let mark = inner
.performance
.mark("did_change_configuration", Some(&params));
let specifiers =
if inner.config.client_capabilities.workspace_configuration {
Some(inner.config.get_specifiers_with_client_uris())
} else {
None
};
(
inner.config.client_capabilities.workspace_configuration,
inner.client.clone(),
specifiers,
mark,
)
};
// start retreiving all the specifiers' settings outside the lock on its own time
if let Some(specifiers) = specifiers {
let language_server = self.clone();
let client = client.clone();
tokio::spawn(async move {
if let Ok(configs) = client
.specifier_configurations(
specifiers.iter().map(|(s)| s.client_uri.clone()).collect(),
)
.await
{
let mut inner = language_server.0.lock().await;
for (i, value) in configs.into_iter().enumerate() {
match value {
Ok(specifier_settings) => {
let entry = specifiers[i].clone();
inner.config.set_specifier_settings(
entry.specifier,
entry.client_uri,
specifier_settings,
);
}
Err(err) => {
error!("{}", err);
}
}
}
}
});
}
// Get the configuration from the client outside of the lock
// in order to prevent potential deadlocking scenarios where
// the server holds a lock and calls into the client, which
// calls into the server which deadlocks acquiring the lock.
// There is a gap here between when the configuration is
// received and acquiring the lock, but most likely there
// won't be any racing here.
let client_workspace_config = if has_workspace_capability {
let config_response = client.workspace_configuration().await;
match config_response {
Ok(value) => Some(value),
Err(err) => {
error!("{}", err);
None
}
}
} else {
None
};
// now update the inner state
let mut inner = self.0.lock().await;
inner
.did_change_configuration(client_workspace_config, params)
.await;
inner.performance.measure(mark);
}
async fn did_change_watched_files(