nu_protocol/pipeline/signals.rs
1use crate::{ShellError, Span};
2use std::sync::{
3 atomic::{AtomicBool, Ordering},
4 Arc,
5};
6
7use serde::{Deserialize, Serialize};
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
87/// The types of things that can be signaled. It's anticipated this will change as we learn more
88/// about how we'd like signals to be handled.
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
90pub enum SignalAction {
91 Interrupt,
92 Reset,
93}