Added env var configuration for language server timeouts

This commit is contained in:
faldor20 2024-02-03 20:53:31 +10:00
parent ff0514aafc
commit 1c7804420a
No known key found for this signature in database
GPG key ID: F2216079B890CD57
3 changed files with 71 additions and 16 deletions

View file

@ -86,3 +86,13 @@ eg: `ROCLS_LOG=debug`
Tests use expect-test, which is a snapshot/expect testing framework. Tests use expect-test, which is a snapshot/expect testing framework.
If a change is made that requires updating the expect tests run `cargo test` confirm that the diff is correct, then run `UPDATE_EXPECT=1 cargo test` to update the contents of the files with the new output. If a change is made that requires updating the expect tests run `cargo test` confirm that the diff is correct, then run `UPDATE_EXPECT=1 cargo test` to update the contents of the files with the new output.
## Config
You can set the environment variables below to control the operation of the language.
`ROCLS_DEBOUNCE_MS`: Sets the amount of time to delay starting analysis of the document when a change comes in. This prevents starting pointless analysis while you are typing normally.
Default: `100`
`ROCLS_LATEST_DOC_TIMEOUT_MS`: Sets the timeout for waiting for an analysis of the latest document to be complete. If a request is sent that needs the latest version of the document to be analyzed, then it will wait up to this duration before just giving up.
Default: `5000`

View file

@ -3,6 +3,7 @@ use log::{debug, info, trace};
use std::{ use std::{
collections::HashMap, collections::HashMap,
sync::{Arc, OnceLock}, sync::{Arc, OnceLock},
time::Duration,
}; };
use tokio::sync::{Mutex, MutexGuard}; use tokio::sync::{Mutex, MutexGuard};
@ -34,12 +35,33 @@ impl DocumentPair {
} }
} }
#[derive(Debug)]
pub(crate) struct RegistryConfig {
pub(crate) latest_document_timeout: Duration,
}
impl Default for RegistryConfig {
fn default() -> Self {
Self {
latest_document_timeout: Duration::from_millis(5000),
}
}
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct Registry { pub(crate) struct Registry {
documents: Mutex<HashMap<Url, DocumentPair>>, documents: Mutex<HashMap<Url, DocumentPair>>,
config: RegistryConfig,
} }
impl Registry { impl Registry {
pub(crate) fn new(config: RegistryConfig) -> Self {
Self {
documents: Default::default(),
config,
}
}
pub async fn get_latest_version(&self, url: &Url) -> Option<i32> { pub async fn get_latest_version(&self, url: &Url) -> Option<i32> {
self.documents.lock().await.get(url).map(|x| x.info.version) self.documents.lock().await.get(url).map(|x| x.info.version)
} }
@ -119,12 +141,12 @@ impl Registry {
async fn document_info_by_url(&self, url: &Url) -> Option<DocInfo> { async fn document_info_by_url(&self, url: &Url) -> Option<DocInfo> {
self.documents.lock().await.get(url).map(|a| a.info.clone()) self.documents.lock().await.get(url).map(|a| a.info.clone())
}U }
///Tries to get the latest document from analysis. ///Tries to get the latest document from analysis.
///Gives up and returns none aft 5 seconds. ///Gives up and returns none aft 5 seconds.
async fn latest_document_by_url(&self, url: &Url) -> Option<Arc<AnalyzedDocument>> { async fn latest_document_by_url(&self, url: &Url) -> Option<Arc<AnalyzedDocument>> {
let duration = std::time::Duration::from_secs(5); tokio::time::timeout(self.config.latest_document_timeout, async {
tokio::time::timeout(duration, async {
//TODO: This should really be a condvar that is triggered by the latest being ready, this will do for now though //TODO: This should really be a condvar that is triggered by the latest being ready, this will do for now though
loop { loop {
let docs = self.documents.lock().await; let docs = self.documents.lock().await;

View file

@ -1,7 +1,7 @@
use analysis::HIGHLIGHT_TOKENS_LEGEND; use analysis::HIGHLIGHT_TOKENS_LEGEND;
use log::{debug, trace}; use log::{debug, trace};
use registry::Registry; use registry::{Registry, RegistryConfig};
use std::future::Future; use std::future::Future;
use std::time::Duration; use std::time::Duration;
@ -15,27 +15,55 @@ mod analysis;
mod convert; mod convert;
mod registry; mod registry;
#[derive(Debug)]
struct RocServer { struct RocServer {
pub state: RocServerState, pub state: RocServerState,
client: Client, client: Client,
} }
struct RocServerConfig {
pub debounce_ms: Duration,
}
impl Default for RocServerConfig {
fn default() -> Self {
Self {
debounce_ms: Duration::from_millis(100),
}
}
}
///This exists so we can test most of RocLs without anything LSP related ///This exists so we can test most of RocLs without anything LSP related
#[derive(Debug)]
struct RocServerState { struct RocServerState {
registry: Registry, registry: Registry,
config: RocServerConfig,
} }
impl std::panic::RefUnwindSafe for RocServer {} impl std::panic::RefUnwindSafe for RocServer {}
fn read_env_num(name: &str) -> Option<u64> {
std::env::var(name)
.ok()
.and_then(|a| str::parse::<u64>(&a).ok())
}
impl RocServer { impl RocServer {
pub fn new(client: Client) -> Self { pub fn new(client: Client) -> Self {
let registry_config = RegistryConfig {
latest_document_timeout: Duration::from_millis(
read_env_num("ROCLS_LATEST_DOC_TIMEOUT_MS").unwrap_or_else(|| 5000),
),
};
let config = RocServerConfig {
debounce_ms: Duration::from_millis(
read_env_num("ROCLS_DEBOUNCE_MS").unwrap_or_else(|| 100),
),
};
Self { Self {
state: RocServerState::new(), state: RocServerState::new(config, Registry::new(registry_config)),
client, client,
} }
} }
pub fn capabilities() -> ServerCapabilities { pub fn capabilities() -> ServerCapabilities {
let text_document_sync = TextDocumentSyncCapability::Options( let text_document_sync = TextDocumentSyncCapability::Options(
// TODO: later on make this incremental // TODO: later on make this incremental
@ -71,7 +99,6 @@ impl RocServer {
let completion_provider = CompletionOptions { let completion_provider = CompletionOptions {
resolve_provider: Some(false), resolve_provider: Some(false),
trigger_characters: Some(vec![".".to_string()]), trigger_characters: Some(vec![".".to_string()]),
//TODO: what is this?
all_commit_characters: None, all_commit_characters: None,
work_done_progress_options: WorkDoneProgressOptions { work_done_progress_options: WorkDoneProgressOptions {
work_done_progress: None, work_done_progress: None,
@ -108,10 +135,8 @@ impl RocServer {
} }
impl RocServerState { impl RocServerState {
pub fn new() -> RocServerState { pub fn new(config: RocServerConfig, registry: Registry) -> RocServerState {
Self { Self { config, registry }
registry: Registry::default(),
}
} }
async fn registry(&self) -> &Registry { async fn registry(&self) -> &Registry {
@ -141,9 +166,7 @@ impl RocServerState {
let inner_ref = self; let inner_ref = self;
let updating_result = async { let updating_result = async {
//This reduces wasted computation by waiting to allow a new change to come in and update the version before we check, but does delay the final analysis. Ideally this would be replaced with cancelling the analysis when a new one comes in. //This reduces wasted computation by waiting to allow a new change to come in and update the version before we check, but does delay the final analysis. Ideally this would be replaced with cancelling the analysis when a new one comes in.
let debounce_ms = std::env::var("ROCLS_DEBOUNCE") tokio::time::sleep(self.config.debounce_ms).await;
.map_or(100, |a| str::parse::<u64>(&a).unwrap_or(100));
tokio::time::sleep(Duration::from_millis(debounce_ms)).await;
let is_latest = inner_ref let is_latest = inner_ref
.registry .registry
.get_latest_version(fi) .get_latest_version(fi)
@ -388,7 +411,7 @@ mod tests {
info!("Doc is:\n{0}", doc); info!("Doc is:\n{0}", doc);
let url = Url::parse("file:/Test.roc").unwrap(); let url = Url::parse("file:/Test.roc").unwrap();
let inner = RocServerState::new(); let inner = RocServerState::new(RocServerConfig::default(), Registry::default());
//setup the file //setup the file
inner.change(&url, doc, 0).await.unwrap(); inner.change(&url, doc, 0).await.unwrap();
(inner, url) (inner, url)