From 3a3f59770416bad40e9f4ecc6cc8b260e3a4537d Mon Sep 17 00:00:00 2001 From: Thomas Otto Date: Wed, 22 Dec 2021 23:19:04 +0100 Subject: Move parent process query into a thread Start the query even before determining if information about the parent process is required (which are most invocations anyhow). --- src/handlers/git_show_file.rs | 4 +--- src/handlers/grep.rs | 11 ++++----- src/handlers/hunk.rs | 12 ++++------ src/main.rs | 6 +++++ src/utils/process.rs | 56 ++++++++++++++++++++++++++++++++----------- 5 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/handlers/git_show_file.rs b/src/handlers/git_show_file.rs index d5eeb512..eb261203 100644 --- a/src/handlers/git_show_file.rs +++ b/src/handlers/git_show_file.rs @@ -9,9 +9,7 @@ impl<'a> StateMachine<'a> { self.painter.emit()?; let mut handled_line = false; if matches!(self.state, State::Unknown) { - if let Some(process::CallingProcess::GitShow(_, extension)) = - process::calling_process().as_deref() - { + if let process::CallingProcess::GitShow(_, extension) = &*process::calling_process() { self.state = State::GitShowFile; self.painter.set_syntax(extension.as_deref()); self.painter.set_highlighter(); diff --git a/src/handlers/grep.rs b/src/handlers/grep.rs index 32e1463c..c3d6da6c 100644 --- a/src/handlers/grep.rs +++ b/src/handlers/grep.rs @@ -246,8 +246,8 @@ fn get_code_style_sections<'b>( } fn make_output_config() -> GrepOutputConfig { - match process::calling_process().as_deref() { - Some(process::CallingProcess::GitGrep(command_line)) + match &*process::calling_process() { + process::CallingProcess::GitGrep(command_line) if command_line.short_options.contains("-W") || command_line.long_options.contains("--function-context") => { @@ -265,7 +265,7 @@ fn make_output_config() -> GrepOutputConfig { pad_line_number: true, } } - Some(process::CallingProcess::GitGrep(command_line)) + process::CallingProcess::GitGrep(command_line) if command_line.short_options.contains("-p") || command_line.long_options.contains("--show-function") => { @@ -380,9 +380,8 @@ pub fn parse_grep_line(line: &str) -> Option { if line.starts_with('{') { ripgrep_json::parse_line(line) } else { - match process::calling_process().as_deref() { - Some(process::CallingProcess::GitGrep(_)) - | Some(process::CallingProcess::OtherGrep) => [ + match &*process::calling_process() { + process::CallingProcess::GitGrep(_) | process::CallingProcess::OtherGrep => [ &*GREP_LINE_REGEX_ASSUMING_FILE_EXTENSION, &*GREP_LINE_REGEX_ASSUMING_NO_INTERNAL_SEPARATOR_CHARS, ] diff --git a/src/handlers/hunk.rs b/src/handlers/hunk.rs index 2acf2eae..b541d326 100644 --- a/src/handlers/hunk.rs +++ b/src/handlers/hunk.rs @@ -27,13 +27,11 @@ lazy_static! { } fn compute_is_word_diff() -> bool { - match process::calling_process().as_deref() { - Some( - CallingProcess::GitDiff(cmd_line) - | CallingProcess::GitShow(cmd_line, _) - | CallingProcess::GitLog(cmd_line) - | CallingProcess::GitReflog(cmd_line), - ) => { + match &*process::calling_process() { + CallingProcess::GitDiff(cmd_line) + | CallingProcess::GitShow(cmd_line, _) + | CallingProcess::GitLog(cmd_line) + | CallingProcess::GitReflog(cmd_line) => { cmd_line.long_options.contains("--word-diff") || cmd_line.long_options.contains("--word-diff-regex") || cmd_line.long_options.contains("--color-words") diff --git a/src/main.rs b/src/main.rs index 5e6eab5e..1de9375b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,6 +65,12 @@ pub mod errors { #[cfg(not(tarpaulin_include))] fn main() -> std::io::Result<()> { + // Do this first because both parsing all the input in `run_app()` and + // listing all processes takes about 50ms on Linux. + // It also improves the chance that the calling process is still around when + // input is piped into delta (e.g. `git show --word-diff=color | delta`). + utils::process::start_determining_calling_process_in_thread(); + // Ignore ctrl-c (SIGINT) to avoid leaving an orphaned pager process. // See https://github.com/dandavison/delta/issues/681 ctrlc::set_handler(|| {}) diff --git a/src/utils/process.rs b/src/utils/process.rs index ed281d8d..26d68d68 100644 --- a/src/utils/process.rs +++ b/src/utils/process.rs @@ -1,9 +1,9 @@ -use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::path::Path; -use sysinfo::{Pid, Process, ProcessExt, ProcessRefreshKind, SystemExt}; +use std::sync::{Arc, Condvar, Mutex, MutexGuard}; use lazy_static::lazy_static; +use sysinfo::{Pid, Process, ProcessExt, ProcessRefreshKind, SystemExt}; #[derive(Clone, Debug, PartialEq)] pub enum CallingProcess { @@ -13,6 +13,8 @@ pub enum CallingProcess { GitReflog(CommandLine), GitGrep(CommandLine), OtherGrep, // rg, grep, ag, ack, etc + None, // no matching process could be found + Pending, // calling process is currently being determined } // TODO: Git blame is currently handled differently @@ -23,23 +25,49 @@ pub struct CommandLine { last_arg: Option, } -pub fn calling_process() -> Option> { - #[cfg(not(test))] - { - CACHED_CALLING_PROCESS.as_ref().map(Cow::Borrowed) - } - #[cfg(test)] - { - determine_calling_process().map(Cow::Owned) - } +lazy_static! { + static ref CALLER: Arc<(Mutex, Condvar)> = + Arc::new((Mutex::new(CallingProcess::Pending), Condvar::new())); } -lazy_static! { - static ref CACHED_CALLING_PROCESS: Option = determine_calling_process(); +pub fn start_determining_calling_process_in_thread() { + // The handle is neither kept nor returned nor joined but dropped, so the main + // thread can exit early if it does not need to know its parent process. + std::thread::Builder::new() + .name("find_calling_process".into()) + .spawn(move || { + let calling_process = determine_calling_process(); + + let (caller_mutex, determine_done) = &**CALLER; + + let mut caller = caller_mutex.lock().unwrap(); + *caller = calling_process; + determine_done.notify_all(); + }) + .unwrap(); +} + +#[cfg(not(test))] +pub fn calling_process() -> MutexGuard<'static, CallingProcess> { + let (caller_mutex, determine_done) = &**CALLER; + + determine_done + .wait_while(caller_mutex.lock().unwrap(), |caller| { + *caller == CallingProcess::Pending + }) + .unwrap() +} + +// The return value is duck-typed to work in place of a MutexGuard when testing. +#[cfg(test)] +pub fn calling_process() -> Box { + type _UnusedImport = MutexGuard<'static, i8>; + Box::new(determine_calling_process()) } -fn determine_calling_process() -> Option { +fn determine_calling_process() -> CallingProcess { calling_process_cmdline(ProcInfo::new(), describe_calling_process) + .unwrap_or(CallingProcess::None) } // Return value of `extract_args(args: &[String]) -> ProcessArgs` function which is -- cgit v1.2.3