diff --git a/crates/compiler/can/src/traverse.rs b/crates/compiler/can/src/traverse.rs index b6445b81f2..3f044eed37 100644 --- a/crates/compiler/can/src/traverse.rs +++ b/crates/compiler/can/src/traverse.rs @@ -1,11 +1,6 @@ //! Traversals over the can ast. -use std::process; - -use roc_module::{ - ident::Lowercase, - symbol::{Interns, Symbol}, -}; +use roc_module::{ident::Lowercase, symbol::Symbol}; use roc_region::all::{Loc, Position, Region}; use roc_types::{subs::Variable, types::MemberImpl}; diff --git a/crates/lang_srv/src/registry.rs b/crates/lang_srv/src/registry.rs index bf0d0792c1..b290c8f197 100644 --- a/crates/lang_srv/src/registry.rs +++ b/crates/lang_srv/src/registry.rs @@ -1,7 +1,12 @@ -use log::{debug, info}; -use std::{collections::HashMap, future::Future, sync::Arc}; +use log::{debug, info, trace, warn}; + +use std::{ + collections::HashMap, + sync::{Arc, OnceLock}, +}; + +use tokio::sync::{Mutex, MutexGuard}; -use tokio::sync::{Mutex, MutexGuard, RwLock, RwLockWriteGuard}; use tower_lsp::lsp_types::{ CompletionResponse, Diagnostic, GotoDefinitionResponse, Hover, Position, SemanticTokensResult, TextEdit, Url, @@ -9,42 +14,26 @@ use tower_lsp::lsp_types::{ use crate::analysis::{AnalyzedDocument, DocInfo}; -#[derive(Debug)] -pub(crate) struct LatestDocument { - pub info: DocInfo, - analyzed: tokio::sync::RwLock>>, -} - -impl LatestDocument { - pub(crate) async fn get_latest(&self) -> Arc { - self.analyzed.read().await.as_ref().unwrap().clone() - } - pub(crate) fn get_lock( - &self, - ) -> impl Future>>> { - self.analyzed.write() - } - pub(crate) fn new(doc_info: DocInfo) -> LatestDocument { - let val = RwLock::new(None); - LatestDocument { - info: doc_info, - analyzed: val, - } - } - pub(crate) fn new_initialised(analyzed: Arc) -> LatestDocument { - LatestDocument { - info: analyzed.doc_info.clone(), - analyzed: RwLock::new(Some(analyzed)), - } - } -} - #[derive(Debug)] pub(crate) struct DocumentPair { - latest_document: Arc, + info: DocInfo, + latest_document: OnceLock>, last_good_document: Arc, } +impl DocumentPair { + pub(crate) fn new( + latest_doc: Arc, + last_good_document: Arc, + ) -> Self { + Self { + info: latest_doc.doc_info.clone(), + latest_document: OnceLock::from(latest_doc), + last_good_document, + } + } +} + #[derive(Debug, Default)] pub(crate) struct Registry { documents: Mutex>, @@ -56,46 +45,33 @@ impl Registry { .lock() .await .get(&url) - .map(|x| x.latest_document.info.version) + .map(|x| x.info.version) } + fn update_document<'a>( documents: &mut MutexGuard<'a, HashMap>, document: Arc, ) { let url = document.url().clone(); - let latest_doc = Arc::new(LatestDocument::new_initialised(document.clone())); match documents.get_mut(&url) { Some(old_doc) => { if document.type_checked() { - *old_doc = DocumentPair { - latest_document: latest_doc, - last_good_document: document, - }; + *old_doc = DocumentPair::new(document.clone(), document); } else { - *old_doc = DocumentPair { - latest_document: latest_doc, - last_good_document: old_doc.last_good_document.clone(), - }; + debug!( + "Document typechecking failed at version {:?}, not updating last_good_document", + &document.doc_info.version + ); + *old_doc = DocumentPair::new(document, old_doc.last_good_document.clone()); } } None => { - documents.insert( - url.clone(), - DocumentPair { - latest_document: latest_doc, - last_good_document: document, - }, - ); + documents.insert(url.clone(), DocumentPair::new(document.clone(), document)); } } } - pub async fn apply_changes<'a>( - &self, - analysed_docs: Vec, - mut partial_writer: RwLockWriteGuard<'a, Option>>, - updating_url: Url, - ) { + pub async fn apply_changes<'a>(&self, analysed_docs: Vec, updating_url: Url) { let mut documents = self.documents.lock().await; debug!( "finised doc analysis for doc: {:?}", @@ -106,13 +82,15 @@ impl Registry { let document = Arc::new(document); //Write the newly analysed document into the partial document that any request requiring the latest document will be waiting on if document.doc_info.url == updating_url { - *partial_writer = Some(document.clone()); + documents + .get_mut(&updating_url) + .map(|a| a.latest_document.set(document.clone()).unwrap()); } Registry::update_document(&mut documents, document); } } - pub async fn apply_doc_info_changes(&self, url: Url, partial: Arc) { + pub async fn apply_doc_info_changes(&self, url: Url, info: DocInfo) { let mut documents_lock = self.documents.lock().await; let doc = documents_lock.get_mut(&url); match doc { @@ -120,26 +98,40 @@ impl Registry { debug!( "set the docInfo for {:?} to version:{:?}", url.as_str(), - partial.info.version + info.version ); - a.latest_document = partial; + *a = DocumentPair { + info, + last_good_document: a.last_good_document.clone(), + latest_document: OnceLock::new(), + }; } None => debug!("no existing docinfo for {:?} ", url.as_str()), } } - fn document_info_by_url(&self, url: &Url) -> Option { - self.documents - .blocking_lock() - .get(url) - .map(|a| a.latest_document.info.clone()) + async fn document_info_by_url(&self, url: &Url) -> Option { + self.documents.lock().await.get(url).map(|a| a.info.clone()) } + ///Tries to get the latest document from analysis. + ///Gives up and returns none after 5 seconds. async fn latest_document_by_url(&self, url: &Url) -> Option> { - match self.documents.lock().await.get(url) { - Some(a) => Some(a.latest_document.get_latest().await), - None => None, + let start = std::time::Instant::now(); + let duration = std::time::Duration::from_secs(5); + + while start.elapsed() < duration { + match self.documents.lock().await.get(url) { + Some(a) => match a.latest_document.get() { + Some(a) => return Some(a.clone()), + None => (), + }, + + None => return None, + } } + warn!("Timed out tring to get latest document"); + None } pub async fn diagnostics(&self, url: &Url) -> Vec { @@ -166,13 +158,13 @@ impl Registry { def_document.definition(symbol) } - pub fn formatting(&self, url: &Url) -> Option> { - let document = self.document_info_by_url(url)?; + pub async fn formatting(&self, url: &Url) -> Option> { + let document = self.document_info_by_url(url).await?; document.format() } - pub fn semantic_tokens(&self, url: &Url) -> Option { - let document = self.document_info_by_url(url)?; + pub async fn semantic_tokens(&self, url: &Url) -> Option { + let document = self.document_info_by_url(url).await?; document.semantic_tokens() } pub async fn completion_items( @@ -180,9 +172,11 @@ impl Registry { url: &Url, position: Position, ) -> Option { + trace!("starting completion "); let lock = self.documents.lock().await; let pair = lock.get(url)?; - let latest_doc_info = &pair.latest_document.info; + + let latest_doc_info = &pair.info; info!( "using document version:{:?} for completion ", latest_doc_info.version diff --git a/crates/lang_srv/src/server.rs b/crates/lang_srv/src/server.rs index 891007a867..740125c8e9 100644 --- a/crates/lang_srv/src/server.rs +++ b/crates/lang_srv/src/server.rs @@ -1,16 +1,14 @@ use analysis::HIGHLIGHT_TOKENS_LEGEND; -use log::debug; +use log::{debug, trace}; use registry::Registry; use std::future::Future; -use tokio::sync::RwLock; -use std::sync::Arc; +use std::time::Duration; use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::*; use tower_lsp::{Client, LanguageServer, LspService, Server}; use crate::analysis::global_analysis; -use crate::registry::LatestDocument; mod analysis; mod convert; @@ -18,12 +16,14 @@ mod registry; #[derive(Debug)] struct RocLs { - pub inner: Arc, + pub inner: Inner, client: Client, } + +///This exists so we can test most of RocLs without anything LSP related #[derive(Debug)] struct Inner { - registry: RwLock, + registry: Registry, } impl std::panic::RefUnwindSafe for RocLs {} @@ -31,7 +31,7 @@ impl std::panic::RefUnwindSafe for RocLs {} impl RocLs { pub fn new(client: Client) -> Self { Self { - inner: Arc::new(Inner::new()), + inner: Inner::new(), client, } } @@ -69,7 +69,7 @@ impl RocLs { full: Some(SemanticTokensFullOptions::Bool(true)), }); let completion_provider = CompletionOptions { - resolve_provider: Some(true), + resolve_provider: Some(false), trigger_characters: Some(vec![".".to_string()]), //TODO: what is this? all_commit_characters: None, @@ -99,7 +99,7 @@ impl RocLs { } debug!("applied_change getting and returning diagnostics"); - let diagnostics = self.inner.registry().await.diagnostics(&fi).await; + let diagnostics = self.inner.registry.diagnostics(&fi).await; self.client .publish_diagnostics(fi, diagnostics, Some(version)) @@ -110,12 +110,12 @@ impl RocLs { impl Inner { pub fn new() -> Inner { Self { - registry: RwLock::new(Registry::default()), + registry: Registry::default(), } } - fn registry(&self) -> impl Future> { - self.registry.read() + async fn registry(&self) -> &Registry { + &self.registry } async fn close(&self, _fi: Url) { @@ -128,30 +128,41 @@ impl Inner { text: String, version: i32, ) -> std::result::Result<(), String> { - debug!("starting change"); - let registry_write_lock = self.registry.write().await; + debug!("V{:?}:starting change", version); + //was write lock - debug!("change aquired registry lock"); + debug!("V{:?}:change aquired registry lock", version); let (results, partial) = global_analysis(fi.clone(), text, version); - let partial_document = Arc::new(LatestDocument::new(partial.clone())); - //TODO check if allowing context switching here is an issue - let partial_doc_write_lock = partial_document.get_lock().await; - registry_write_lock - .apply_doc_info_changes(fi.clone(), partial_document.clone()) + self.registry + .apply_doc_info_changes(fi.clone(), partial.clone()) .await; //Now that we've got our new partial document written and we hold the exclusive write_handle to its analysis we can allow other tasks to access the registry and the doc_info inside this partial document - drop(registry_write_lock); - debug!("finished updating docinfo, starting analysis ",); + debug!( + "V{:?}:finished updating docinfo, starting analysis ", + version + ); - let inner_ref = self.clone(); + let inner_ref = self; let updating_result = async { + //This reduces wasted computation by waiting to see if a new change comes in, but does delay the final analysis. Ideally this would be replaced with cancelling the analysis when a new one comes in. + tokio::time::sleep(Duration::from_millis(100)).await; + let is_latest = inner_ref + .registry + .get_latest_version(fi) + .await + .map(|latest| latest == version) + .unwrap_or(true); + if !is_latest { + return Err("Not latest version skipping analysis".to_string()); + } + let results = match tokio::task::spawn_blocking(results).await { Err(e) => return Err(format!("Document analysis failed. reason:{:?}", e)), Ok(a) => a, }; - let latest_version = inner_ref.registry.read().await.get_latest_version(fi).await; + let latest_version = inner_ref.registry.get_latest_version(fi).await; //if this version is not the latest another change must have come in and this analysis is useless //if there is no older version we can just proceed with the update @@ -163,18 +174,16 @@ impl Inner { )); } } - debug!("finished updating documents returning ",); + debug!( + "V{:?}:finished document analysis applying changes ", + version + ); - inner_ref - .registry - .write() - .await - .apply_changes(results, partial_doc_write_lock, fi.clone()) - .await; + inner_ref.registry.apply_changes(results, fi.clone()).await; Ok(()) } .await; - debug!("finished updating documents returning ",); + debug!("V{:?}:finished document change process", version); updating_result } } @@ -209,6 +218,7 @@ impl LanguageServer for RocLs { let TextDocumentContentChangeEvent { text, .. } = params.content_changes.into_iter().next().unwrap(); + trace!("got did_change"); self.change(uri, text, version).await; } @@ -233,8 +243,7 @@ impl LanguageServer for RocLs { panic_wrapper_async(|| async { self.inner - .registry() - .await + .registry .hover(&text_document.uri, position) .await }) @@ -272,8 +281,14 @@ impl LanguageServer for RocLs { work_done_progress_params: _, } = params; - panic_wrapper_async(|| async { self.inner.registry().await.formatting(&text_document.uri) }) - .await + panic_wrapper_async(|| async { + self.inner + .registry() + .await + .formatting(&text_document.uri) + .await + }) + .await } async fn semantic_tokens_full( @@ -291,15 +306,16 @@ impl LanguageServer for RocLs { .registry() .await .semantic_tokens(&text_document.uri) + .await }) .await } async fn completion(&self, params: CompletionParams) -> Result> { let doc = params.text_document_position; + trace!("got completion request"); let res = panic_wrapper_async(|| async { self.inner - .registry() - .await + .registry .completion_items(&doc.text_document.uri, doc.position) .await }) @@ -320,7 +336,7 @@ where } } -#[tokio::main] +#[tokio::main(flavor = "multi_thread")] async fn main() { env_logger::init();