nu_protocol/pipeline/
signals.rs

1use crate::{ShellError, Span};
2use nu_glob::Interruptible;
3use serde::{Deserialize, Serialize};
4use std::sync::{
5    Arc,
6    atomic::{AtomicBool, Ordering},
7};
8
9/// Used to check for signals to suspend or terminate the execution of Nushell code.
10///
11/// For now, this struct only supports interruption (ctrl+c or SIGINT).
12#[derive(Debug, Clone)]
13pub struct Signals {
14    signals: Option<Arc<AtomicBool>>,
15}
16
17impl Signals {
18    /// A [`Signals`] that is not hooked up to any event/signals source.
19    ///
20    /// So, this [`Signals`] will never be interrupted.
21    pub const EMPTY: Self = Signals { signals: None };
22
23    /// Create a new [`Signals`] with `ctrlc` as the interrupt source.
24    ///
25    /// Once `ctrlc` is set to `true`, [`check`](Self::check) will error
26    /// and [`interrupted`](Self::interrupted) will return `true`.
27    pub fn new(ctrlc: Arc<AtomicBool>) -> Self {
28        Self {
29            signals: Some(ctrlc),
30        }
31    }
32
33    /// Create a [`Signals`] that is not hooked up to any event/signals source.
34    ///
35    /// So, the returned [`Signals`] will never be interrupted.
36    ///
37    /// This should only be used in test code, or if the stream/iterator being created
38    /// already has an underlying [`Signals`].
39    pub const fn empty() -> Self {
40        Self::EMPTY
41    }
42
43    /// Returns an `Err` if an interrupt has been triggered.
44    ///
45    /// Otherwise, returns `Ok`.
46    #[inline]
47    pub fn check(&self, span: Span) -> Result<(), ShellError> {
48        #[inline]
49        #[cold]
50        fn interrupt_error(span: Span) -> Result<(), ShellError> {
51            Err(ShellError::Interrupted { span })
52        }
53
54        if self.interrupted() {
55            interrupt_error(span)
56        } else {
57            Ok(())
58        }
59    }
60
61    /// Triggers an interrupt.
62    pub fn trigger(&self) {
63        if let Some(signals) = &self.signals {
64            signals.store(true, Ordering::Relaxed);
65        }
66    }
67
68    /// Returns whether an interrupt has been triggered.
69    #[inline]
70    pub fn interrupted(&self) -> bool {
71        self.signals
72            .as_deref()
73            .is_some_and(|b| b.load(Ordering::Relaxed))
74    }
75
76    pub(crate) fn is_empty(&self) -> bool {
77        self.signals.is_none()
78    }
79
80    pub fn reset(&self) {
81        if let Some(signals) = &self.signals {
82            signals.store(false, Ordering::Relaxed);
83        }
84    }
85}
86
87impl Interruptible for Signals {
88    #[inline]
89    fn interrupted(&self) -> bool {
90        self.interrupted()
91    }
92}
93
94/// The types of things that can be signaled. It's anticipated this will change as we learn more
95/// about how we'd like signals to be handled.
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
97pub enum SignalAction {
98    Interrupt,
99    Reset,
100}