Skip to content

Commit

Permalink
chore(els): add a client health checker
Browse files Browse the repository at this point in the history
  • Loading branch information
mtshiba committed Aug 20, 2023
1 parent a0ce142 commit 6b9629e
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 26 deletions.
5 changes: 5 additions & 0 deletions crates/els/channels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub struct SendChannels {
signature_help: mpsc::Sender<(i64, SignatureHelpParams)>,
will_rename_files: mpsc::Sender<(i64, RenameFilesParams)>,
execute_command: mpsc::Sender<(i64, ExecuteCommandParams)>,
pub(crate) health_check: mpsc::Sender<()>,
}

impl SendChannels {
Expand All @@ -50,6 +51,7 @@ impl SendChannels {
let (tx_sig_help, rx_sig_help) = mpsc::channel();
let (tx_will_rename_files, rx_will_rename_files) = mpsc::channel();
let (tx_execute_command, rx_execute_command) = mpsc::channel();
let (tx_health_check, rx_health_check) = mpsc::channel();
(
Self {
completion: tx_completion,
Expand All @@ -66,6 +68,7 @@ impl SendChannels {
signature_help: tx_sig_help,
will_rename_files: tx_will_rename_files,
execute_command: tx_execute_command,
health_check: tx_health_check,
},
ReceiveChannels {
completion: rx_completion,
Expand All @@ -82,6 +85,7 @@ impl SendChannels {
signature_help: rx_sig_help,
will_rename_files: rx_will_rename_files,
execute_command: rx_execute_command,
health_check: rx_health_check,
},
)
}
Expand All @@ -103,6 +107,7 @@ pub struct ReceiveChannels {
pub(crate) signature_help: mpsc::Receiver<(i64, SignatureHelpParams)>,
pub(crate) will_rename_files: mpsc::Receiver<(i64, RenameFilesParams)>,
pub(crate) execute_command: mpsc::Receiver<(i64, ExecuteCommandParams)>,
pub(crate) health_check: mpsc::Receiver<()>,
}

pub trait Sendable<R: lsp_types::request::Request + 'static> {
Expand Down
50 changes: 47 additions & 3 deletions crates/els/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
use std::sync::mpsc::Receiver;
use std::thread::sleep;
use std::time::Duration;

use erg_common::consts::PYTHON_MODE;
use erg_common::dict::Dict;
use erg_common::fn_name;
use erg_common::spawn::spawn_new_thread;
use erg_common::style::*;
use erg_common::traits::Stream;
use erg_common::{fn_name, lsp_log};
use erg_compiler::artifact::BuildRunnable;
use erg_compiler::erg_parser::ast::Module;
use erg_compiler::erg_parser::parse::Parsable;
use erg_compiler::error::CompileErrors;

use lsp_types::{
Diagnostic, DiagnosticSeverity, NumberOrString, Position, PublishDiagnosticsParams, Range, Url,
ConfigurationParams, Diagnostic, DiagnosticSeverity, NumberOrString, Position,
PublishDiagnosticsParams, Range, Url,
};
use serde_json::json;

use crate::diff::{ASTDiff, HIRDiff};
use crate::server::{send, send_log, AnalysisResult, DefaultFeatures, ELSResult, Server};
use crate::server::{
send, send_log, AnalysisResult, DefaultFeatures, ELSResult, Server, HEALTH_CHECKER_ID,
};
use crate::util::{self, NormalizedUrl};

impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
Expand Down Expand Up @@ -240,4 +244,44 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
fn_name!(),
);
}

/// Send an empty `workspace/configuration` request periodically.
/// 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<()>) {
// let mut self_ = self.clone();
spawn_new_thread(
move || {
loop {
// send_log("checking client health").unwrap();
let params = ConfigurationParams { items: vec![] };
send(&json!({
"jsonrpc": "2.0",
"id": HEALTH_CHECKER_ID,
"method": "workspace/configuration",
"params": params,
}))
.unwrap();
sleep(Duration::from_secs(10));
}
},
"start_client_health_checker_sender",
);
spawn_new_thread(
move || {
loop {
match receiver.recv_timeout(Duration::from_secs(20)) {
Ok(_) => {
// send_log("client health check passed").unwrap();
}
Err(_) => {
// send_log("client health check timed out").unwrap();
lsp_log!("client health check timed out");
std::process::exit(1);
}
}
}
},
"start_client_health_checker_receiver",
);
}
}
65 changes: 42 additions & 23 deletions crates/els/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ use crate::hir_visitor::HIRVisitor;
use crate::message::{ErrorMessage, LSPResult, LogMessage, ShowMessage};
use crate::util::{self, NormalizedUrl};

pub const HEALTH_CHECKER_ID: i64 = 10000;

pub type ELSResult<T> = Result<T, Box<dyn std::error::Error>>;

pub type ErgLanguageServer = Server<HIRBuilder>;
Expand Down Expand Up @@ -404,21 +406,32 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
}
}
let mut result = InitializeResult::default();
result.capabilities = ServerCapabilities::default();
self.file_cache.set_capabilities(&mut result.capabilities);
result.capabilities = self.init_capabilities();
self.init_services();
send(&json!({
"jsonrpc": "2.0",
"id": id,
"result": result,
}))
}

#[allow(clippy::field_reassign_with_default)]
fn init_capabilities(&mut self) -> ServerCapabilities {
let mut capabilities = ServerCapabilities::default();
self.file_cache.set_capabilities(&mut capabilities);
let mut comp_options = CompletionOptions::default();
comp_options.trigger_characters = Some(TRIGGER_CHARS.map(String::from).to_vec());
comp_options.resolve_provider = Some(true);
result.capabilities.completion_provider = Some(comp_options);
result.capabilities.rename_provider = Some(OneOf::Left(true));
result.capabilities.references_provider = Some(OneOf::Left(true));
result.capabilities.definition_provider = Some(OneOf::Left(true));
result.capabilities.hover_provider = self
capabilities.completion_provider = Some(comp_options);
capabilities.rename_provider = Some(OneOf::Left(true));
capabilities.references_provider = Some(OneOf::Left(true));
capabilities.definition_provider = Some(OneOf::Left(true));
capabilities.hover_provider = self
.disabled_features
.contains(&DefaultFeatures::Hover)
.not()
.then_some(HoverProviderCapability::Simple(true));
result.capabilities.inlay_hint_provider = self
capabilities.inlay_hint_provider = self
.disabled_features
.contains(&DefaultFeatures::InlayHint)
.not()
Expand Down Expand Up @@ -449,14 +462,14 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
],
token_modifiers: vec![],
};
result.capabilities.semantic_tokens_provider = self
capabilities.semantic_tokens_provider = self
.disabled_features
.contains(&DefaultFeatures::SemanticTokens)
.not()
.then_some(SemanticTokensServerCapabilities::SemanticTokensOptions(
sema_options,
));
result.capabilities.code_action_provider = if self
capabilities.code_action_provider = if self
.disabled_features
.contains(&DefaultFeatures::CodeAction)
{
Expand All @@ -469,11 +482,11 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
});
Some(options)
};
result.capabilities.execute_command_provider = Some(ExecuteCommandOptions {
capabilities.execute_command_provider = Some(ExecuteCommandOptions {
commands: vec![format!("{}.eliminate_unused_vars", self.mode())],
work_done_progress_options: WorkDoneProgressOptions::default(),
});
result.capabilities.signature_help_provider = self
capabilities.signature_help_provider = self
.disabled_features
.contains(&DefaultFeatures::SignatureHelp)
.not()
Expand All @@ -484,15 +497,10 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
work_done_progress: None,
},
});
result.capabilities.code_lens_provider = Some(CodeLensOptions {
capabilities.code_lens_provider = Some(CodeLensOptions {
resolve_provider: Some(false),
});
self.init_services();
send(&json!({
"jsonrpc": "2.0",
"id": id,
"result": result,
}))
capabilities
}

fn init_services(&mut self) {
Expand Down Expand Up @@ -537,6 +545,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
Self::handle_execute_command,
);
self.start_auto_diagnostics();
self.start_client_health_checker(receivers.health_check);
}

fn exit(&self) -> ELSResult<()> {
Expand Down Expand Up @@ -626,10 +635,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
msg.get("method").and_then(|m| m.as_str()),
) {
(Some(id), Some(method)) => self.handle_request(&msg, id, method),
(Some(_id), None) => {
// ignore at this time
Ok(())
}
(Some(id), None) => self.handle_response(id, &msg),
(None, Some(notification)) => self.handle_notification(&msg, notification),
_ => send_invalid_req_error(),
}
Expand Down Expand Up @@ -733,6 +739,19 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
}
}

fn handle_response(&mut self, id: i64, msg: &Value) -> ELSResult<()> {
match id {
HEALTH_CHECKER_ID => {
self.channels.as_ref().unwrap().health_check.send(())?;
}
_ => {
_log!("received a unknown response: {msg}");
// ignore at this time
}
}
Ok(())
}

pub(crate) fn get_checker(&self, path: PathBuf) -> Checker {
if let Some(shared) = self.get_shared() {
let shared = shared.clone();
Expand Down

0 comments on commit 6b9629e

Please sign in to comment.