use self::process::{Process, StderrMode, spawn_process};
use self::reader::spawn_reader;
use self::routing::Shared;
use parking_lot::Mutex;
use rootle_provider::{Capabilities, ProviderResult};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::thread::JoinHandle;
use std::time::Duration;
mod handshake;
mod process;
mod reader;
mod restart;
mod routing;
mod stderr;
mod trace;
mod transport;
mod wire;
#[cfg(test)]
mod tests;
pub const SEARCH_LANGUAGES: &str = include_str!("../examples/languages.tsv");
pub const DEFAULT_TIMEOUT: Duration = Duration::from_millis(30_000);
pub struct StdioProvider {
name: String,
icon: Option<String>,
capabilities: Capabilities,
process: Mutex<Process>,
shared: Arc<Shared>,
current_id: AtomicU64,
timeout: Duration,
command: Vec<String>,
env: Vec<(String, String)>,
stderr_mode: StderrMode,
stderr_reader: Mutex<Option<stderr::StderrReader>>,
reader: Mutex<Option<JoinHandle<()>>>,
notice: Mutex<Option<String>>,
failure_noticed: Mutex<bool>,
closed: AtomicBool,
cache_bytes: u64,
cache_dir: Option<std::path::PathBuf>,
cache_used: parking_lot::Mutex<Option<u64>>,
}
impl Drop for StdioProvider {
fn drop(&mut self) {
self.closed.store(true, Ordering::Release);
let process = self.process.get_mut();
process.terminate();
if let Some(handle) = self.reader.lock().take() {
let _ = handle.join();
}
if let Some(handle) = self.stderr_reader.lock().take() {
let _ = handle.join();
}
}
}
#[derive(Default)]
struct SpawnCache {
bytes: u64,
directory: Option<std::path::PathBuf>,
}
impl StdioProvider {
pub fn spawn(command: &[String], timeout: Duration) -> ProviderResult<Self> {
Self::spawn_inner(
command,
timeout,
&[],
StderrMode::Null,
SpawnCache::default(),
)
}
pub fn spawn_with_stderr(
command: &[String],
timeout: Duration,
inherit_stderr: bool,
) -> ProviderResult<Self> {
Self::spawn_with_cache(command, timeout, inherit_stderr, 0, None)
}
pub fn spawn_with_cache(
command: &[String],
timeout: Duration,
inherit_stderr: bool,
cache_bytes: u64,
cache_dir: Option<std::path::PathBuf>,
) -> ProviderResult<Self> {
let mode = if inherit_stderr {
StderrMode::Inherit
} else {
StderrMode::Null
};
Self::spawn_inner(
command,
timeout,
&[],
mode,
SpawnCache {
bytes: cache_bytes,
directory: cache_dir,
},
)
}
pub fn spawn_with_env(
command: &[String],
timeout: Duration,
env: &[(&str, &str)],
) -> ProviderResult<Self> {
Self::spawn_inner(
command,
timeout,
env,
StderrMode::Null,
SpawnCache::default(),
)
}
fn spawn_inner(
command: &[String],
timeout: Duration,
env: &[(&str, &str)],
stderr_mode: StderrMode,
cache: SpawnCache,
) -> ProviderResult<Self> {
let spawned = spawn_process(command, env, stderr_mode);
let shared = Arc::new(Shared::default());
let session = shared.routing.lock().session;
let (mut process, stdout, child_stderr) = match spawned {
Ok(spawned) => {
self::trace::lifecycle("spawn", |fields| {
fields.insert("session".into(), serde_json::json!(session.value()));
});
spawned
}
Err(error) => {
self::trace::lifecycle("spawn_failed", |fields| {
fields.insert("session".into(), serde_json::json!(session.value()));
});
return Err(error);
}
};
let reader = spawn_reader(stdout, Arc::clone(&shared), session)?;
let stderr_handle = match child_stderr
.map(|stderr| self::stderr::spawn(stderr, session))
.transpose()
{
Ok(handle) => handle,
Err(error) => {
rootle_trace::mark_incomplete("provider stderr reader could not start");
process.terminate();
let _ = reader.join();
return Err(rootle_provider::ProviderError::other(format!(
"start provider stderr reader: {error}"
)));
}
};
let provider = StdioProvider {
name: "stdio".into(),
icon: None,
capabilities: Capabilities {
commit: false,
orgs: true,
code_search: true,
file_search: true,
refs: false,
log: false,
blame: false,
},
process: Mutex::new(process),
shared,
current_id: AtomicU64::new(0),
timeout,
command: command.to_vec(),
env: env
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
stderr_mode,
stderr_reader: Mutex::new(stderr_handle),
reader: Mutex::new(Some(reader)),
notice: Mutex::new(None),
failure_noticed: Mutex::new(false),
closed: AtomicBool::new(false),
cache_bytes: cache.bytes,
cache_dir: cache.directory,
cache_used: parking_lot::Mutex::new(None),
};
let reply = provider.handshake()?;
provider.initialize_from(reply)
}
}