diff options
-rw-r--r-- | cli/flags.rs | 37 | ||||
-rw-r--r-- | cli/lsp/mod.rs | 7 | ||||
-rw-r--r-- | cli/lsp/parent_process_checker.rs | 70 | ||||
-rw-r--r-- | cli/main.rs | 6 |
4 files changed, 112 insertions, 8 deletions
diff --git a/cli/flags.rs b/cli/flags.rs index 295bdb317..9fc3103b4 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -84,7 +84,9 @@ pub enum DenoSubcommand { root: Option<PathBuf>, force: bool, }, - Lsp, + Lsp { + parent_pid: Option<u32>, + }, Lint { files: Vec<PathBuf>, ignore: Vec<PathBuf>, @@ -876,6 +878,16 @@ go-to-definition support and automatic code formatting. How to connect various editors and IDEs to 'deno lsp': https://deno.land/manual/getting_started/setup_your_environment#editors-and-ides") + .arg( + Arg::with_name("parent-pid") + .long("parent-pid") + .help("The parent process id to periodically check for the existence of or exit") + .takes_value(true) + .validator(|val: String| match val.parse::<usize>() { + Ok(_) => Ok(()), + Err(_) => Err("parent-pid should be a number".to_string()), + }), + ) } fn lint_subcommand<'a, 'b>() -> App<'a, 'b> { @@ -1621,8 +1633,11 @@ fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) { }; } -fn lsp_parse(flags: &mut Flags, _matches: &clap::ArgMatches) { - flags.subcommand = DenoSubcommand::Lsp; +fn lsp_parse(flags: &mut Flags, matches: &clap::ArgMatches) { + let parent_pid = matches + .value_of("parent-pid") + .map(|val| val.parse().unwrap()); + flags.subcommand = DenoSubcommand::Lsp { parent_pid }; } fn lint_parse(flags: &mut Flags, matches: &clap::ArgMatches) { @@ -2308,10 +2323,24 @@ mod tests { assert_eq!( r.unwrap(), Flags { - subcommand: DenoSubcommand::Lsp, + subcommand: DenoSubcommand::Lsp { parent_pid: None }, ..Flags::default() } ); + + let r = flags_from_vec(svec!["deno", "lsp", "--parent-pid", "5"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Lsp { + parent_pid: Some(5), + }, + ..Flags::default() + } + ); + + let r = flags_from_vec(svec!["deno", "lsp", "--parent-pid", "invalid-arg"]); + assert!(r.is_err()); } #[test] diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs index 367257911..4723f8b56 100644 --- a/cli/lsp/mod.rs +++ b/cli/lsp/mod.rs @@ -13,6 +13,7 @@ mod diagnostics; mod documents; pub(crate) mod language_server; mod lsp_custom; +mod parent_process_checker; mod path_to_regex; mod performance; mod registries; @@ -22,10 +23,14 @@ mod text; mod tsc; mod urls; -pub async fn start() -> Result<(), AnyError> { +pub async fn start(parent_pid: Option<u32>) -> Result<(), AnyError> { let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); + if let Some(parent_pid) = parent_pid { + parent_process_checker::start(parent_pid); + } + let (service, messages) = LspService::new(language_server::LanguageServer::new); Server::new(stdin, stdout) diff --git a/cli/lsp/parent_process_checker.rs b/cli/lsp/parent_process_checker.rs new file mode 100644 index 000000000..6c80969f4 --- /dev/null +++ b/cli/lsp/parent_process_checker.rs @@ -0,0 +1,70 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use tokio::time::sleep; +use tokio::time::Duration; + +/// Starts a task that will check for the existence of the +/// provided process id. Once that process no longer exists +/// it will terminate the current process. +pub fn start(parent_process_id: u32) { + tokio::task::spawn(async move { + loop { + sleep(Duration::from_secs(30)).await; + + if !is_process_active(parent_process_id) { + eprintln!("Parent process lost. Exiting."); + std::process::exit(1); + } + } + }); +} + +#[cfg(unix)] +fn is_process_active(process_id: u32) -> bool { + unsafe { + // signal of 0 checks for the existence of the process id + libc::kill(process_id as i32, 0) == 0 + } +} + +#[cfg(windows)] +fn is_process_active(process_id: u32) -> bool { + use winapi::shared::minwindef::DWORD; + use winapi::shared::minwindef::FALSE; + use winapi::shared::ntdef::NULL; + use winapi::shared::winerror::WAIT_TIMEOUT; + use winapi::um::handleapi::CloseHandle; + use winapi::um::processthreadsapi::OpenProcess; + use winapi::um::synchapi::WaitForSingleObject; + use winapi::um::winnt::SYNCHRONIZE; + + unsafe { + let process = OpenProcess(SYNCHRONIZE, FALSE, process_id as DWORD); + let result = if process == NULL { + false + } else { + WaitForSingleObject(process, 0) == WAIT_TIMEOUT + }; + CloseHandle(process); + result + } +} + +#[cfg(test)] +mod test { + use super::is_process_active; + use std::process::Command; + use test_util::deno_exe_path; + + #[test] + fn process_active() { + // launch a long running process + let mut child = Command::new(deno_exe_path()).arg("lsp").spawn().unwrap(); + + let pid = child.id(); + assert_eq!(is_process_active(pid), true); + child.kill().unwrap(); + child.wait().unwrap(); + assert_eq!(is_process_active(pid), false); + } +} diff --git a/cli/main.rs b/cli/main.rs index 3354f4399..9941d737a 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -470,8 +470,8 @@ async fn install_command( tools::installer::install(flags, &module_url, args, name, root, force) } -async fn lsp_command() -> Result<(), AnyError> { - lsp::start().await +async fn lsp_command(parent_pid: Option<u32>) -> Result<(), AnyError> { + lsp::start(parent_pid).await } async fn lint_command( @@ -1264,7 +1264,7 @@ fn get_subcommand( } => { install_command(flags, module_url, args, name, root, force).boxed_local() } - DenoSubcommand::Lsp => lsp_command().boxed_local(), + DenoSubcommand::Lsp { parent_pid } => lsp_command(parent_pid).boxed_local(), DenoSubcommand::Lint { files, rules, |