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: *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 /// Returns the underlying interrupt flag, if any.
87 ///
88 /// This can be passed to external iterators (e.g. dc-glob) that need raw access to the
89 /// same flag so they can be cancelled when Ctrl+C is pressed.
90 pub fn interrupt_flag(&self) -> Option<Arc<AtomicBool>> {
91 self.signals.clone()
92 }
93}
94
95impl Interruptible for Signals {
96 #[inline]
97 fn interrupted(&self) -> bool {
98 self.interrupted()
99 }
100}
101
102/// The types of things that can be signaled. It's anticipated this will change as we learn more
103/// about how we'd like signals to be handled.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
105pub enum SignalAction {
106 Interrupt,
107 Reset,
108}