Merge pull request #451 from erg-lang/fix-els

Enhance ELS stability
This commit is contained in:
Shunsuke Shibayama 2023-08-23 18:20:13 +09:00 committed by GitHub
commit 01fae890a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 244 additions and 67 deletions

View file

@ -16,23 +16,35 @@ use lsp_types::{
use crate::server::Server; use crate::server::Server;
#[derive(Debug, Clone)]
pub enum WorkerMessage<P> {
Request(i64, P),
Kill,
}
impl<P> From<(i64, P)> for WorkerMessage<P> {
fn from((id, params): (i64, P)) -> Self {
Self::Request(id, params)
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SendChannels { pub struct SendChannels {
completion: mpsc::Sender<(i64, CompletionParams)>, completion: mpsc::Sender<WorkerMessage<CompletionParams>>,
resolve_completion: mpsc::Sender<(i64, CompletionItem)>, resolve_completion: mpsc::Sender<WorkerMessage<CompletionItem>>,
goto_definition: mpsc::Sender<(i64, GotoDefinitionParams)>, goto_definition: mpsc::Sender<WorkerMessage<GotoDefinitionParams>>,
semantic_tokens_full: mpsc::Sender<(i64, SemanticTokensParams)>, semantic_tokens_full: mpsc::Sender<WorkerMessage<SemanticTokensParams>>,
inlay_hint: mpsc::Sender<(i64, InlayHintParams)>, inlay_hint: mpsc::Sender<WorkerMessage<InlayHintParams>>,
inlay_hint_resolve: mpsc::Sender<(i64, InlayHint)>, inlay_hint_resolve: mpsc::Sender<WorkerMessage<InlayHint>>,
hover: mpsc::Sender<(i64, HoverParams)>, hover: mpsc::Sender<WorkerMessage<HoverParams>>,
references: mpsc::Sender<(i64, ReferenceParams)>, references: mpsc::Sender<WorkerMessage<ReferenceParams>>,
code_lens: mpsc::Sender<(i64, CodeLensParams)>, code_lens: mpsc::Sender<WorkerMessage<CodeLensParams>>,
code_action: mpsc::Sender<(i64, CodeActionParams)>, code_action: mpsc::Sender<WorkerMessage<CodeActionParams>>,
code_action_resolve: mpsc::Sender<(i64, CodeAction)>, code_action_resolve: mpsc::Sender<WorkerMessage<CodeAction>>,
signature_help: mpsc::Sender<(i64, SignatureHelpParams)>, signature_help: mpsc::Sender<WorkerMessage<SignatureHelpParams>>,
will_rename_files: mpsc::Sender<(i64, RenameFilesParams)>, will_rename_files: mpsc::Sender<WorkerMessage<RenameFilesParams>>,
execute_command: mpsc::Sender<(i64, ExecuteCommandParams)>, execute_command: mpsc::Sender<WorkerMessage<ExecuteCommandParams>>,
pub(crate) health_check: mpsc::Sender<()>, pub(crate) health_check: mpsc::Sender<WorkerMessage<()>>,
} }
impl SendChannels { impl SendChannels {
@ -89,25 +101,43 @@ impl SendChannels {
}, },
) )
} }
pub(crate) fn close(&self) {
self.completion.send(WorkerMessage::Kill).unwrap();
self.resolve_completion.send(WorkerMessage::Kill).unwrap();
self.goto_definition.send(WorkerMessage::Kill).unwrap();
self.semantic_tokens_full.send(WorkerMessage::Kill).unwrap();
self.inlay_hint.send(WorkerMessage::Kill).unwrap();
self.inlay_hint_resolve.send(WorkerMessage::Kill).unwrap();
self.hover.send(WorkerMessage::Kill).unwrap();
self.references.send(WorkerMessage::Kill).unwrap();
self.code_lens.send(WorkerMessage::Kill).unwrap();
self.code_action.send(WorkerMessage::Kill).unwrap();
self.code_action_resolve.send(WorkerMessage::Kill).unwrap();
self.signature_help.send(WorkerMessage::Kill).unwrap();
self.will_rename_files.send(WorkerMessage::Kill).unwrap();
self.execute_command.send(WorkerMessage::Kill).unwrap();
self.health_check.send(WorkerMessage::Kill).unwrap();
}
} }
#[derive(Debug)] #[derive(Debug)]
pub struct ReceiveChannels { pub struct ReceiveChannels {
pub(crate) completion: mpsc::Receiver<(i64, CompletionParams)>, pub(crate) completion: mpsc::Receiver<WorkerMessage<CompletionParams>>,
pub(crate) resolve_completion: mpsc::Receiver<(i64, CompletionItem)>, pub(crate) resolve_completion: mpsc::Receiver<WorkerMessage<CompletionItem>>,
pub(crate) goto_definition: mpsc::Receiver<(i64, GotoDefinitionParams)>, pub(crate) goto_definition: mpsc::Receiver<WorkerMessage<GotoDefinitionParams>>,
pub(crate) semantic_tokens_full: mpsc::Receiver<(i64, SemanticTokensParams)>, pub(crate) semantic_tokens_full: mpsc::Receiver<WorkerMessage<SemanticTokensParams>>,
pub(crate) inlay_hint: mpsc::Receiver<(i64, InlayHintParams)>, pub(crate) inlay_hint: mpsc::Receiver<WorkerMessage<InlayHintParams>>,
pub(crate) inlay_hint_resolve: mpsc::Receiver<(i64, InlayHint)>, pub(crate) inlay_hint_resolve: mpsc::Receiver<WorkerMessage<InlayHint>>,
pub(crate) hover: mpsc::Receiver<(i64, HoverParams)>, pub(crate) hover: mpsc::Receiver<WorkerMessage<HoverParams>>,
pub(crate) references: mpsc::Receiver<(i64, ReferenceParams)>, pub(crate) references: mpsc::Receiver<WorkerMessage<ReferenceParams>>,
pub(crate) code_lens: mpsc::Receiver<(i64, CodeLensParams)>, pub(crate) code_lens: mpsc::Receiver<WorkerMessage<CodeLensParams>>,
pub(crate) code_action: mpsc::Receiver<(i64, CodeActionParams)>, pub(crate) code_action: mpsc::Receiver<WorkerMessage<CodeActionParams>>,
pub(crate) code_action_resolve: mpsc::Receiver<(i64, CodeAction)>, pub(crate) code_action_resolve: mpsc::Receiver<WorkerMessage<CodeAction>>,
pub(crate) signature_help: mpsc::Receiver<(i64, SignatureHelpParams)>, pub(crate) signature_help: mpsc::Receiver<WorkerMessage<SignatureHelpParams>>,
pub(crate) will_rename_files: mpsc::Receiver<(i64, RenameFilesParams)>, pub(crate) will_rename_files: mpsc::Receiver<WorkerMessage<RenameFilesParams>>,
pub(crate) execute_command: mpsc::Receiver<(i64, ExecuteCommandParams)>, pub(crate) execute_command: mpsc::Receiver<WorkerMessage<ExecuteCommandParams>>,
pub(crate) health_check: mpsc::Receiver<()>, pub(crate) health_check: mpsc::Receiver<WorkerMessage<()>>,
} }
pub trait Sendable<R: lsp_types::request::Request + 'static> { pub trait Sendable<R: lsp_types::request::Request + 'static> {
@ -124,7 +154,7 @@ macro_rules! impl_sendable {
.as_ref() .as_ref()
.unwrap() .unwrap()
.$receiver .$receiver
.send((id, params)) .send($crate::channels::WorkerMessage::Request(id, params))
.unwrap(); .unwrap();
} }
} }

View file

@ -423,8 +423,8 @@ impl CompletionCache {
self.cache.borrow_mut().insert(namespace, items); self.cache.borrow_mut().insert(namespace, items);
} }
pub fn _clear(&self, namespace: &str) { pub fn clear(&self) {
self.cache.borrow_mut().remove(namespace); self.cache.borrow_mut().clear();
} }
pub fn _append(&self, cache: Dict<String, Vec<CompletionItem>>) { pub fn _append(&self, cache: Dict<String, Vec<CompletionItem>>) {

View file

@ -19,9 +19,12 @@ use lsp_types::{
}; };
use serde_json::json; use serde_json::json;
use crate::_log;
use crate::channels::WorkerMessage;
use crate::diff::{ASTDiff, HIRDiff}; use crate::diff::{ASTDiff, HIRDiff};
use crate::server::{ use crate::server::{
send, send_log, AnalysisResult, DefaultFeatures, ELSResult, Server, HEALTH_CHECKER_ID, send, send_log, AnalysisResult, DefaultFeatures, ELSResult, Server, ASK_AUTO_SAVE_ID,
HEALTH_CHECKER_ID,
}; };
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
@ -43,6 +46,12 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} else { } else {
"exec" "exec"
}; };
if let Some((old, new)) = self.analysis_result.get_ast(&uri).zip(self.get_ast(&uri)) {
if ASTDiff::diff(old, &new).is_nop() {
crate::_log!("no changes: {uri}");
return Ok(());
}
}
let mut checker = self.get_checker(path.clone()); let mut checker = self.get_checker(path.clone());
let artifact = match checker.build(code.into(), mode) { let artifact = match checker.build(code.into(), mode) {
Ok(artifact) => { Ok(artifact) => {
@ -198,7 +207,8 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} }
let params = PublishDiagnosticsParams::new(uri, diagnostics, None); let params = PublishDiagnosticsParams::new(uri, diagnostics, None);
if self if self
.client_capas .init_params
.capabilities
.text_document .text_document
.as_ref() .as_ref()
.map(|doc| doc.publish_diagnostics.is_some()) .map(|doc| doc.publish_diagnostics.is_some())
@ -223,6 +233,25 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
move || { move || {
let mut file_vers = Dict::<NormalizedUrl, i32>::new(); let mut file_vers = Dict::<NormalizedUrl, i32>::new();
loop { loop {
if _self
.client_answers
.borrow()
.get(&ASK_AUTO_SAVE_ID)
.is_none()
{
_self.ask_auto_save().unwrap();
} else if _self
.client_answers
.borrow()
.get(&ASK_AUTO_SAVE_ID)
.is_some_and(|val| {
val["result"].as_array().and_then(|a| a[0].as_str())
== Some("afterDelay")
})
{
_log!("Auto saving is enabled");
break;
}
for uri in _self.file_cache.entries() { for uri in _self.file_cache.entries() {
let Some(latest_ver) = _self.file_cache.get_ver(&uri) else { let Some(latest_ver) = _self.file_cache.get_ver(&uri) else {
continue; continue;
@ -247,8 +276,11 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
/// Send an empty `workspace/configuration` request periodically. /// Send an empty `workspace/configuration` request periodically.
/// If there is no response to the request within a certain period of time, terminate the server. /// If there is no response to the request within a certain period of time, terminate the server.
pub fn start_client_health_checker(&self, receiver: Receiver<()>) { pub fn start_client_health_checker(&self, receiver: Receiver<WorkerMessage<()>>) {
const INTERVAL: Duration = Duration::from_secs(5);
const TIMEOUT: Duration = Duration::from_secs(10);
// let mut self_ = self.clone(); // let mut self_ = self.clone();
// FIXME: close this thread when the server is restarted
spawn_new_thread( spawn_new_thread(
move || { move || {
loop { loop {
@ -261,7 +293,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
"params": params, "params": params,
})) }))
.unwrap(); .unwrap();
sleep(Duration::from_secs(10)); sleep(INTERVAL);
} }
}, },
"start_client_health_checker_sender", "start_client_health_checker_sender",
@ -269,14 +301,20 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
spawn_new_thread( spawn_new_thread(
move || { move || {
loop { loop {
match receiver.recv_timeout(Duration::from_secs(20)) { match receiver.recv_timeout(TIMEOUT) {
Ok(WorkerMessage::Kill) => {
break;
}
Ok(_) => { Ok(_) => {
// send_log("client health check passed").unwrap(); // send_log("client health check passed").unwrap();
} }
Err(_) => { Err(_) => {
// send_log("client health check timed out").unwrap(); lsp_log!("Client health check timed out");
lsp_log!("client health check timed out"); // lsp_log!("Restart the server");
std::process::exit(1); // _log!("Restart the server");
// send_error_info("Something went wrong, ELS has been restarted").unwrap();
// self_.restart();
panic!("Client health check timed out");
} }
} }
} }

View file

@ -63,6 +63,10 @@ impl ASTDiff {
.unwrap_or(Self::Nop), .unwrap_or(Self::Nop),
} }
} }
pub const fn is_nop(&self) -> bool {
matches!(self, Self::Nop)
}
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]

View file

@ -58,6 +58,10 @@ impl FileCache {
} }
} }
pub fn clear(&self) {
self.files.borrow_mut().clear();
}
fn load_once(&self, uri: &NormalizedUrl) -> ELSResult<()> { fn load_once(&self, uri: &NormalizedUrl) -> ELSResult<()> {
if self.files.borrow_mut().get(uri).is_some() { if self.files.borrow_mut().get(uri).is_some() {
return Ok(()); return Ok(());

View file

@ -32,19 +32,20 @@ use lsp_types::request::{
WillRenameFiles, WillRenameFiles,
}; };
use lsp_types::{ use lsp_types::{
ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, CodeLensOptions,
CodeLensOptions, CompletionOptions, DidChangeTextDocumentParams, DidOpenTextDocumentParams, CompletionOptions, ConfigurationItem, ConfigurationParams, DidChangeTextDocumentParams,
ExecuteCommandOptions, HoverProviderCapability, InitializeResult, InlayHintOptions, DidOpenTextDocumentParams, ExecuteCommandOptions, HoverProviderCapability, InitializeParams,
InlayHintServerCapabilities, OneOf, Position, SemanticTokenType, SemanticTokensFullOptions, InitializeResult, InlayHintOptions, InlayHintServerCapabilities, OneOf, Position,
SemanticTokensLegend, SemanticTokensOptions, SemanticTokensServerCapabilities, SemanticTokenType, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
ServerCapabilities, SignatureHelpOptions, WorkDoneProgressOptions, SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelpOptions,
WorkDoneProgressOptions,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use serde_json::Value; use serde_json::Value;
use crate::channels::{SendChannels, Sendable}; use crate::channels::{SendChannels, Sendable, WorkerMessage};
use crate::completion::CompletionCache; use crate::completion::CompletionCache;
use crate::file_cache::FileCache; use crate::file_cache::FileCache;
use crate::hir_visitor::HIRVisitor; use crate::hir_visitor::HIRVisitor;
@ -52,6 +53,7 @@ use crate::message::{ErrorMessage, LSPResult, LogMessage, ShowMessage};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
pub const HEALTH_CHECKER_ID: i64 = 10000; pub const HEALTH_CHECKER_ID: i64 = 10000;
pub const ASK_AUTO_SAVE_ID: i64 = 10001;
pub type ELSResult<T> = Result<T, Box<dyn std::error::Error>>; pub type ELSResult<T> = Result<T, Box<dyn std::error::Error>>;
@ -312,7 +314,8 @@ pub struct Server<Checker: BuildRunnable = HIRBuilder, Parser: Parsable = Simple
pub(crate) cfg: ErgConfig, pub(crate) cfg: ErgConfig,
pub(crate) home: PathBuf, pub(crate) home: PathBuf,
pub(crate) erg_path: PathBuf, pub(crate) erg_path: PathBuf,
pub(crate) client_capas: ClientCapabilities, pub(crate) init_params: InitializeParams,
pub(crate) client_answers: Shared<Dict<i64, Value>>,
pub(crate) disabled_features: Vec<DefaultFeatures>, pub(crate) disabled_features: Vec<DefaultFeatures>,
pub(crate) opt_features: Vec<OptionalFeatures>, pub(crate) opt_features: Vec<OptionalFeatures>,
pub(crate) file_cache: FileCache, pub(crate) file_cache: FileCache,
@ -332,7 +335,8 @@ impl<C: BuildRunnable, P: Parsable> Clone for Server<C, P> {
cfg: self.cfg.clone(), cfg: self.cfg.clone(),
home: self.home.clone(), home: self.home.clone(),
erg_path: self.erg_path.clone(), erg_path: self.erg_path.clone(),
client_capas: self.client_capas.clone(), init_params: self.init_params.clone(),
client_answers: self.client_answers.clone(),
disabled_features: self.disabled_features.clone(), disabled_features: self.disabled_features.clone(),
opt_features: self.opt_features.clone(), opt_features: self.opt_features.clone(),
file_cache: self.file_cache.clone(), file_cache: self.file_cache.clone(),
@ -354,7 +358,8 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
cfg, cfg,
home: normalize_path(std::env::current_dir().unwrap_or_default()), home: normalize_path(std::env::current_dir().unwrap_or_default()),
erg_path: erg_path().clone(), // already normalized erg_path: erg_path().clone(), // already normalized
client_capas: ClientCapabilities::default(), init_params: InitializeParams::default(),
client_answers: Shared::new(Dict::new()),
disabled_features: vec![], disabled_features: vec![],
opt_features: vec![], opt_features: vec![],
file_cache: FileCache::new(), file_cache: FileCache::new(),
@ -390,7 +395,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
send_log("initializing ELS")?; send_log("initializing ELS")?;
// #[allow(clippy::collapsible_if)] // #[allow(clippy::collapsible_if)]
if msg.get("params").is_some() && msg["params"].get("capabilities").is_some() { if msg.get("params").is_some() && msg["params"].get("capabilities").is_some() {
self.client_capas = ClientCapabilities::deserialize(&msg["params"]["capabilities"])?; self.init_params = InitializeParams::deserialize(&msg["params"])?;
// send_log(format!("set client capabilities: {:?}", self.client_capas))?; // send_log(format!("set client capabilities: {:?}", self.client_capas))?;
} }
let mut args = self.cfg.runtime_args.iter(); let mut args = self.cfg.runtime_args.iter();
@ -503,7 +508,22 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
capabilities capabilities
} }
fn init_services(&mut self) { pub(crate) fn ask_auto_save(&self) -> ELSResult<()> {
let params = ConfigurationParams {
items: vec![ConfigurationItem {
scope_uri: None,
section: Some("files.autoSave".to_string()),
}],
};
send(&json!({
"jsonrpc": "2.0",
"id": ASK_AUTO_SAVE_ID,
"method": "workspace/configuration",
"params": params,
}))
}
fn start_language_services(&mut self) {
let (senders, receivers) = SendChannels::new(); let (senders, receivers) = SendChannels::new();
self.channels = Some(senders); self.channels = Some(senders);
self.start_service::<Completion>(receivers.completion, Self::handle_completion); self.start_service::<Completion>(receivers.completion, Self::handle_completion);
@ -544,10 +564,14 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
receivers.execute_command, receivers.execute_command,
Self::handle_execute_command, Self::handle_execute_command,
); );
self.start_auto_diagnostics();
self.start_client_health_checker(receivers.health_check); self.start_client_health_checker(receivers.health_check);
} }
fn init_services(&mut self) {
self.start_language_services();
self.start_auto_diagnostics();
}
fn exit(&self) -> ELSResult<()> { fn exit(&self) -> ELSResult<()> {
send_log("exiting ELS")?; send_log("exiting ELS")?;
std::process::exit(0); std::process::exit(0);
@ -562,6 +586,17 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
})) }))
} }
#[allow(unused)]
pub(crate) fn restart(&mut self) {
self.file_cache.clear();
self.comp_cache.clear();
self.modules = ModuleCache::new();
self.analysis_result = AnalysisResultCache::new();
self.current_sig = None;
self.channels.as_ref().unwrap().close();
self.start_language_services();
}
/// Copied and modified from RLS, https://github.com/rust-lang/rls/blob/master/rls/src/server/io.rs /// Copied and modified from RLS, https://github.com/rust-lang/rls/blob/master/rls/src/server/io.rs
fn read_message(&self) -> Result<Value, io::Error> { fn read_message(&self) -> Result<Value, io::Error> {
// Read in the "Content-Length: xx" part. // Read in the "Content-Length: xx" part.
@ -654,7 +689,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
fn start_service<R>( fn start_service<R>(
&self, &self,
receiver: mpsc::Receiver<(i64, R::Params)>, receiver: mpsc::Receiver<WorkerMessage<R::Params>>,
handler: Handler<Server<Checker, Parser>, R::Params, R::Result>, handler: Handler<Server<Checker, Parser>, R::Params, R::Result>,
) where ) where
R: lsp_types::request::Request + 'static, R: lsp_types::request::Request + 'static,
@ -664,8 +699,15 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
let mut _self = self.clone(); let mut _self = self.clone();
spawn_new_thread( spawn_new_thread(
move || loop { move || loop {
let (id, params) = receiver.recv().unwrap(); let msg = receiver.recv().unwrap();
match msg {
WorkerMessage::Request(id, params) => {
let _ = send(&LSPResult::new(id, handler(&mut _self, params).unwrap())); let _ = send(&LSPResult::new(id, handler(&mut _self, params).unwrap()));
}
WorkerMessage::Kill => {
break;
}
}
}, },
fn_name!(), fn_name!(),
); );
@ -742,16 +784,25 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
fn handle_response(&mut self, id: i64, msg: &Value) -> ELSResult<()> { fn handle_response(&mut self, id: i64, msg: &Value) -> ELSResult<()> {
match id { match id {
HEALTH_CHECKER_ID => { HEALTH_CHECKER_ID => {
self.channels.as_ref().unwrap().health_check.send(())?; self.channels
.as_ref()
.unwrap()
.health_check
.send(WorkerMessage::Request(0, ()))?;
} }
_ => { _ => {
_log!("received a unknown response: {msg}"); _log!("msg: {msg}");
// ignore at this time if msg.get("error").is_none() {
self.client_answers.borrow_mut().insert(id, msg.clone());
}
} }
} }
Ok(()) Ok(())
} }
/// TODO: Reuse cache.
/// Because of the difficulty of caching "transitional types" such as assert casting and mutable dependent types,
/// the cache is deleted after each analysis.
pub(crate) fn get_checker(&self, path: PathBuf) -> Checker { pub(crate) fn get_checker(&self, path: PathBuf) -> Checker {
if let Some(shared) = self.get_shared() { if let Some(shared) = self.get_shared() {
let shared = shared.clone(); let shared = shared.clone();
@ -892,9 +943,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
if let Some(module) = self.modules.remove(uri) { if let Some(module) = self.modules.remove(uri) {
let shared = module.context.shared(); let shared = module.context.shared();
let path = util::uri_to_path(uri); let path = util::uri_to_path(uri);
shared.mod_cache.remove(&path); shared.clear(&path);
shared.index.remove_path(&path);
shared.graph.remove(&path);
} }
} }
} }

View file

@ -50,3 +50,8 @@ where
.spawn(run) .spawn(run)
.unwrap() .unwrap()
} }
pub fn safe_yield() {
std::thread::yield_now();
std::thread::sleep(std::time::Duration::from_millis(10));
}

View file

@ -254,6 +254,10 @@ pub trait Stream<T>: Sized {
fn split_off(&mut self, at: usize) -> Vec<T> { fn split_off(&mut self, at: usize) -> Vec<T> {
self.ref_mut_payload().split_off(at) self.ref_mut_payload().split_off(at)
} }
fn retain(&mut self, f: impl FnMut(&T) -> bool) {
self.ref_mut_payload().retain(f);
}
} }
#[macro_export] #[macro_export]

View file

@ -54,27 +54,37 @@ impl SharedCompilerResource {
_self _self
} }
/// Clear all but builtin modules
pub fn clear_all(&self) { pub fn clear_all(&self) {
self.mod_cache.initialize(); self.mod_cache.initialize();
self.py_mod_cache.initialize(); self.py_mod_cache.initialize();
self.index.initialize(); self.index.initialize();
self.graph.initialize(); self.graph.initialize();
self.trait_impls.initialize(); // self.trait_impls.initialize();
self.promises.initialize();
self.errors.clear(); self.errors.clear();
self.warns.clear(); self.warns.clear();
} }
/// Clear all information about the module. All child modules are also cleared.
pub fn clear(&self, path: &Path) { pub fn clear(&self, path: &Path) {
for child in self.graph.children(path) {
self.clear(&child);
}
self.mod_cache.remove(path); self.mod_cache.remove(path);
self.py_mod_cache.remove(path); self.py_mod_cache.remove(path);
self.index.remove_path(path); self.index.remove_path(path);
self.graph.remove(path); self.graph.remove(path);
self.promises.remove(path);
// self.errors.remove(path);
// self.warns.remove(path);
} }
pub fn rename_path(&self, old: &Path, new: PathBuf) { pub fn rename_path(&self, old: &Path, new: PathBuf) {
self.mod_cache.rename_path(old, new.clone()); self.mod_cache.rename_path(old, new.clone());
self.py_mod_cache.rename_path(old, new.clone()); self.py_mod_cache.rename_path(old, new.clone());
self.index.rename_path(old, new.clone()); self.index.rename_path(old, new.clone());
self.graph.rename_path(old, new); self.graph.rename_path(old, new.clone());
self.promises.rename(old, new);
} }
} }

View file

@ -137,6 +137,7 @@ impl ModuleGraph {
Ok(()) Ok(())
} }
/// Do not erase relationships with modules that depend on `path`
pub fn remove(&mut self, path: &Path) { pub fn remove(&mut self, path: &Path) {
let path = NormalizedPathBuf::new(path.to_path_buf()); let path = NormalizedPathBuf::new(path.to_path_buf());
self.0.retain(|n| n.id != path); self.0.retain(|n| n.id != path);

View file

@ -61,6 +61,15 @@ impl TraitImpls {
self.cache.remove(path) self.cache.remove(path)
} }
pub fn rename<Q: Eq + Hash + ?Sized>(&mut self, old: &Q, new: Str)
where
Str: Borrow<Q>,
{
if let Some(impls) = self.remove(old) {
self.register(new, impls);
}
}
pub fn initialize(&mut self) { pub fn initialize(&mut self) {
self.cache.clear(); self.cache.clear();
} }
@ -123,6 +132,13 @@ impl SharedTraitImpls {
self.0.borrow_mut().remove(path) self.0.borrow_mut().remove(path)
} }
pub fn rename<Q: Eq + Hash + ?Sized>(&self, old: &Q, new: Str)
where
Str: Borrow<Q>,
{
self.0.borrow_mut().rename(old, new);
}
pub fn ref_inner(&self) -> MappedRwLockReadGuard<Dict<Str, Set<TraitImpl>>> { pub fn ref_inner(&self) -> MappedRwLockReadGuard<Dict<Str, Set<TraitImpl>>> {
RwLockReadGuard::map(self.0.borrow(), |tis| &tis.cache) RwLockReadGuard::map(self.0.borrow(), |tis| &tis.cache)
} }

View file

@ -5,6 +5,7 @@ use std::thread::{current, JoinHandle, ThreadId};
use erg_common::dict::Dict; use erg_common::dict::Dict;
use erg_common::pathutil::NormalizedPathBuf; use erg_common::pathutil::NormalizedPathBuf;
use erg_common::shared::Shared; use erg_common::shared::Shared;
use erg_common::spawn::safe_yield;
use super::SharedModuleGraph; use super::SharedModuleGraph;
@ -102,6 +103,21 @@ impl SharedPromises {
.insert(path, Promise::running(handle)); .insert(path, Promise::running(handle));
} }
pub fn remove(&self, path: &Path) {
self.promises.borrow_mut().remove(path);
}
pub fn initialize(&self) {
self.promises.borrow_mut().clear();
}
pub fn rename(&self, old: &Path, new: PathBuf) {
let Some(promise) = self.promises.borrow_mut().remove(old) else {
return;
};
self.promises.borrow_mut().insert(new.into(), promise);
}
pub fn is_registered(&self, path: &Path) -> bool { pub fn is_registered(&self, path: &Path) -> bool {
self.promises.borrow().get(path).is_some() self.promises.borrow().get(path).is_some()
} }
@ -140,7 +156,7 @@ impl SharedPromises {
.get(path) .get(path)
.is_some_and(|p| !p.is_finished()) .is_some_and(|p| !p.is_finished())
{ {
std::thread::yield_now(); safe_yield();
} }
return Ok(()); return Ok(());
} }
@ -152,7 +168,7 @@ impl SharedPromises {
pub fn join(&self, path: &Path) -> std::thread::Result<()> { pub fn join(&self, path: &Path) -> std::thread::Result<()> {
while let Some(Promise::Joining) | None = self.promises.borrow().get(path) { while let Some(Promise::Joining) | None = self.promises.borrow().get(path) {
std::thread::yield_now(); safe_yield();
} }
let promise = self.promises.borrow_mut().get_mut(path).unwrap().take(); let promise = self.promises.borrow_mut().get_mut(path).unwrap().take();
self.join_checked(path, promise) self.join_checked(path, promise)