use super::{create_command, gc::PluginGc, make_plugin_interface, PluginInterface, PluginSource};
use nu_protocol::{
engine::{EngineState, Stack},
PluginGcConfig, PluginIdentity, RegisteredPlugin, ShellError,
};
use std::{
ffi::OsStr,
sync::{Arc, Mutex},
};
#[doc(hidden)]
#[derive(Debug)]
pub struct PersistentPlugin {
identity: PluginIdentity,
mutable: Mutex<MutableState>,
}
#[derive(Debug)]
struct MutableState {
running: Option<RunningPlugin>,
gc_config: PluginGcConfig,
}
#[derive(Debug)]
struct RunningPlugin {
pid: u32,
interface: PluginInterface,
gc: PluginGc,
}
impl PersistentPlugin {
pub fn new(identity: PluginIdentity, gc_config: PluginGcConfig) -> PersistentPlugin {
PersistentPlugin {
identity,
mutable: Mutex::new(MutableState {
running: None,
gc_config,
}),
}
}
pub(crate) fn get<E, K, V>(
self: Arc<Self>,
envs: impl FnOnce() -> Result<E, ShellError>,
) -> Result<PluginInterface, ShellError>
where
E: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
let mut mutable = self.mutable.lock().map_err(|_| ShellError::NushellFailed {
msg: format!(
"plugin `{}` mutex poisoned, probably panic during spawn",
self.identity.name()
),
})?;
if let Some(ref running) = mutable.running {
Ok(running.interface.clone())
} else {
let new_running = self.clone().spawn(envs()?, &mutable.gc_config)?;
let interface = new_running.interface.clone();
mutable.running = Some(new_running);
Ok(interface)
}
}
fn spawn(
self: Arc<Self>,
envs: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
gc_config: &PluginGcConfig,
) -> Result<RunningPlugin, ShellError> {
let source_file = self.identity.filename();
let mut plugin_cmd = create_command(source_file, self.identity.shell());
plugin_cmd.envs(envs);
let program_name = plugin_cmd.get_program().to_os_string().into_string();
let child = plugin_cmd.spawn().map_err(|err| {
let error_msg = match err.kind() {
std::io::ErrorKind::NotFound => match program_name {
Ok(prog_name) => {
format!("Can't find {prog_name}, please make sure that {prog_name} is in PATH.")
}
_ => {
format!("Error spawning child process: {err}")
}
},
_ => {
format!("Error spawning child process: {err}")
}
};
ShellError::PluginFailedToLoad { msg: error_msg }
})?;
let gc = PluginGc::new(gc_config.clone(), &self)?;
let pid = child.id();
let interface =
make_plugin_interface(child, Arc::new(PluginSource::new(self)), Some(gc.clone()))?;
Ok(RunningPlugin { pid, interface, gc })
}
}
impl RegisteredPlugin for PersistentPlugin {
fn identity(&self) -> &PluginIdentity {
&self.identity
}
fn is_running(&self) -> bool {
self.mutable
.lock()
.map(|m| m.running.is_some())
.unwrap_or(false)
}
fn pid(&self) -> Option<u32> {
self.mutable
.lock()
.ok()
.and_then(|r| r.running.as_ref().map(|r| r.pid))
}
fn stop(&self) -> Result<(), ShellError> {
let mut mutable = self.mutable.lock().map_err(|_| ShellError::NushellFailed {
msg: format!(
"plugin `{}` mutable mutex poisoned, probably panic during spawn",
self.identity.name()
),
})?;
if let Some(ref running) = mutable.running {
running.gc.stop_tracking();
}
mutable.running = None;
Ok(())
}
fn set_gc_config(&self, gc_config: &PluginGcConfig) {
if let Ok(mut mutable) = self.mutable.lock() {
mutable.gc_config = gc_config.clone();
if let Some(gc) = mutable.running.as_ref().map(|running| running.gc.clone()) {
drop(mutable);
gc.set_config(gc_config.clone());
gc.flush();
}
}
}
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync> {
self
}
}
#[doc(hidden)]
pub trait GetPlugin: RegisteredPlugin {
fn get_plugin(
self: Arc<Self>,
context: Option<(&EngineState, &mut Stack)>,
) -> Result<PluginInterface, ShellError>;
}
impl GetPlugin for PersistentPlugin {
fn get_plugin(
self: Arc<Self>,
context: Option<(&EngineState, &mut Stack)>,
) -> Result<PluginInterface, ShellError> {
self.get(|| {
let envs = context
.map(|(engine_state, stack)| {
let stack = &mut stack.start_capture();
nu_engine::env::env_to_strings(engine_state, stack)
})
.transpose()?;
Ok(envs.into_iter().flatten())
})
}
}