1use crate::{
2 PluginGc,
3 init::{create_command, make_plugin_interface},
4};
5
6use super::{PluginInterface, PluginSource};
7use nu_plugin_core::CommunicationMode;
8use nu_protocol::{
9 HandlerGuard, Handlers, PluginGcConfig, PluginIdentity, PluginMetadata, RegisteredPlugin,
10 ShellError,
11 engine::{EngineState, Stack},
12 shell_error::io::IoError,
13};
14use std::{
15 collections::HashMap,
16 sync::{Arc, Mutex},
17};
18
19#[derive(Debug)]
23pub struct PersistentPlugin {
24 identity: PluginIdentity,
26 mutable: Mutex<MutableState>,
28}
29
30#[derive(Debug)]
33struct MutableState {
34 running: Option<RunningPlugin>,
36 metadata: Option<PluginMetadata>,
38 preferred_mode: Option<PreferredCommunicationMode>,
40 gc_config: PluginGcConfig,
42 signal_guard: Option<HandlerGuard>,
44}
45
46#[derive(Debug, Clone, Copy)]
47enum PreferredCommunicationMode {
48 Stdio,
49 #[cfg(feature = "local-socket")]
50 LocalSocket,
51}
52
53#[derive(Debug)]
54struct RunningPlugin {
55 interface: PluginInterface,
57 gc: PluginGc,
59}
60
61impl PersistentPlugin {
62 pub fn new(identity: PluginIdentity, gc_config: PluginGcConfig) -> PersistentPlugin {
64 PersistentPlugin {
65 identity,
66 mutable: Mutex::new(MutableState {
67 running: None,
68 metadata: None,
69 preferred_mode: None,
70 gc_config,
71 signal_guard: None,
72 }),
73 }
74 }
75
76 pub fn get(
81 self: Arc<Self>,
82 envs: impl FnOnce() -> Result<HashMap<String, String>, ShellError>,
83 ) -> Result<PluginInterface, ShellError> {
84 let mut mutable = self.mutable.lock().map_err(|_| ShellError::NushellFailed {
85 msg: format!(
86 "plugin `{}` mutex poisoned, probably panic during spawn",
87 self.identity.name()
88 ),
89 })?;
90
91 if let Some(ref running) = mutable.running {
92 Ok(running.interface.clone())
94 } else {
95 let envs = envs()?;
104 let result = self.clone().spawn(&envs, &mut mutable);
105
106 if result.is_err()
109 && !matches!(
110 mutable.preferred_mode,
111 Some(PreferredCommunicationMode::Stdio)
112 )
113 {
114 log::warn!(
115 "{}: Trying again with stdio communication because mode {:?} failed with {result:?}",
116 self.identity.name(),
117 mutable.preferred_mode
118 );
119 mutable.preferred_mode = Some(PreferredCommunicationMode::Stdio);
121 self.clone().spawn(&envs, &mut mutable)?;
122 }
123
124 Ok(mutable
125 .running
126 .as_ref()
127 .ok_or_else(|| ShellError::NushellFailed {
128 msg: "spawn() succeeded but didn't set interface".into(),
129 })?
130 .interface
131 .clone())
132 }
133 }
134
135 fn spawn(
137 self: Arc<Self>,
138 envs: &HashMap<String, String>,
139 mutable: &mut MutableState,
140 ) -> Result<(), ShellError> {
141 if let Some(running) = mutable.running.take() {
143 running.gc.stop_tracking();
145 }
146
147 let source_file = self.identity.filename();
148
149 let mode = match mutable.preferred_mode {
151 Some(PreferredCommunicationMode::Stdio) | None => CommunicationMode::Stdio,
153 #[cfg(feature = "local-socket")]
155 Some(PreferredCommunicationMode::LocalSocket) => {
156 CommunicationMode::local_socket(source_file)
157 }
158 };
159
160 let mut plugin_cmd = create_command(source_file, self.identity.shell(), &mode);
161
162 plugin_cmd.envs(envs);
165
166 let program_name = plugin_cmd.get_program().to_os_string().into_string();
167
168 let comm = mode.serve()?;
170
171 let child = plugin_cmd.spawn().map_err(|err| {
173 let error_msg = match err.kind() {
174 std::io::ErrorKind::NotFound => match program_name {
175 Ok(prog_name) => {
176 format!(
177 "Can't find {prog_name}, please make sure that {prog_name} is in PATH."
178 )
179 }
180 _ => {
181 format!("Error spawning child process: {err}")
182 }
183 },
184 _ => {
185 format!("Error spawning child process: {err}")
186 }
187 };
188 ShellError::PluginFailedToLoad { msg: error_msg }
189 })?;
190
191 let gc = PluginGc::new(mutable.gc_config.clone(), &self).map_err(|err| {
193 IoError::new_internal(err, "Could not start plugin gc", nu_protocol::location!())
194 })?;
195
196 let pid = child.id();
197 let interface = make_plugin_interface(
198 child,
199 comm,
200 Arc::new(PluginSource::new(self.clone())),
201 Some(pid),
202 Some(gc.clone()),
203 )?;
204
205 #[cfg(feature = "local-socket")]
208 if mutable.preferred_mode.is_none()
209 && interface
210 .protocol_info()?
211 .supports_feature(&nu_plugin_protocol::Feature::LocalSocket)
212 {
213 log::trace!(
214 "{}: Attempting to upgrade to local socket mode",
215 self.identity.name()
216 );
217 gc.stop_tracking();
220 mutable.preferred_mode = Some(PreferredCommunicationMode::LocalSocket);
222 return self.spawn(envs, mutable);
223 }
224
225 mutable.running = Some(RunningPlugin { interface, gc });
226 Ok(())
227 }
228
229 fn stop_internal(&self, reset: bool) -> Result<(), ShellError> {
230 let mut mutable = self.mutable.lock().map_err(|_| ShellError::NushellFailed {
231 msg: format!(
232 "plugin `{}` mutable mutex poisoned, probably panic during spawn",
233 self.identity.name()
234 ),
235 })?;
236
237 if let Some(ref running) = mutable.running {
240 running.gc.stop_tracking();
241 }
242
243 mutable.running = None;
246
247 if reset {
249 mutable.preferred_mode = None;
250 }
251 Ok(())
252 }
253}
254
255impl RegisteredPlugin for PersistentPlugin {
256 fn identity(&self) -> &PluginIdentity {
257 &self.identity
258 }
259
260 fn is_running(&self) -> bool {
261 self.mutable
264 .lock()
265 .map(|m| m.running.is_some())
266 .unwrap_or(false)
267 }
268
269 fn pid(&self) -> Option<u32> {
270 self.mutable
272 .lock()
273 .ok()
274 .and_then(|r| r.running.as_ref().and_then(|r| r.interface.pid()))
275 }
276
277 fn stop(&self) -> Result<(), ShellError> {
278 self.stop_internal(false)
279 }
280
281 fn reset(&self) -> Result<(), ShellError> {
282 self.stop_internal(true)
283 }
284
285 fn metadata(&self) -> Option<PluginMetadata> {
286 self.mutable.lock().ok().and_then(|m| m.metadata.clone())
287 }
288
289 fn set_metadata(&self, metadata: Option<PluginMetadata>) {
290 if let Ok(mut mutable) = self.mutable.lock() {
291 mutable.metadata = metadata;
292 }
293 }
294
295 fn set_gc_config(&self, gc_config: &PluginGcConfig) {
296 if let Ok(mut mutable) = self.mutable.lock() {
297 mutable.gc_config = gc_config.clone();
299
300 if let Some(gc) = mutable.running.as_ref().map(|running| running.gc.clone()) {
302 drop(mutable);
304 gc.set_config(gc_config.clone());
305 gc.flush();
306 }
307 }
308 }
309
310 fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync> {
311 self
312 }
313
314 fn configure_signal_handler(self: Arc<Self>, handlers: &Handlers) -> Result<(), ShellError> {
315 let guard = {
316 let plugin = Arc::downgrade(&self);
319 handlers.register(Box::new(move |action| {
320 if let Some(plugin) = plugin.upgrade() {
323 if let Ok(mutable) = plugin.mutable.lock() {
324 if let Some(ref running) = mutable.running {
325 let _ = running.interface.signal(action);
326 }
327 }
328 }
329 }))?
330 };
331
332 if let Ok(mut mutable) = self.mutable.lock() {
333 mutable.signal_guard = Some(guard);
334 }
335
336 Ok(())
337 }
338}
339
340pub trait GetPlugin: RegisteredPlugin {
342 fn get_plugin(
345 self: Arc<Self>,
346 context: Option<(&EngineState, &mut Stack)>,
347 ) -> Result<PluginInterface, ShellError>;
348}
349
350impl GetPlugin for PersistentPlugin {
351 fn get_plugin(
352 self: Arc<Self>,
353 mut context: Option<(&EngineState, &mut Stack)>,
354 ) -> Result<PluginInterface, ShellError> {
355 self.get(|| {
356 let envs = context
358 .as_mut()
359 .map(|(engine_state, stack)| {
360 let stack = &mut stack.start_collect_value();
364 nu_engine::env::env_to_strings(engine_state, stack)
365 })
366 .transpose()?;
367
368 Ok(envs.unwrap_or_default())
369 })
370 }
371}