nautilus_live/node/
state.rs1use std::sync::{
17 Arc,
18 atomic::{AtomicU8, Ordering},
19};
20
21use super::metrics::{RunnerMetrics, RunnerMetricsSnapshot};
22
23const STOP_REQUESTED: u8 = 1 << 7;
24const STATE_MASK: u8 = !STOP_REQUESTED;
25
26#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
28#[repr(u8)]
29#[cfg_attr(
30 feature = "python",
31 pyo3::pyclass(
32 frozen,
33 eq,
34 eq_int,
35 module = "nautilus_trader.live",
36 from_py_object,
37 rename_all = "SCREAMING_SNAKE_CASE",
38 )
39)]
40#[cfg_attr(
41 feature = "python",
42 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.live")
43)]
44pub enum NodeState {
45 #[default]
46 Idle = 0,
47 Starting = 1,
48 Running = 2,
49 ShuttingDown = 3,
50 Stopped = 4,
51}
52
53impl NodeState {
54 #[must_use]
60 pub const fn from_u8(value: u8) -> Self {
61 match value {
62 0 => Self::Idle,
63 1 => Self::Starting,
64 2 => Self::Running,
65 3 => Self::ShuttingDown,
66 4 => Self::Stopped,
67 _ => panic!("Invalid NodeState value"),
68 }
69 }
70
71 #[must_use]
73 pub const fn as_u8(self) -> u8 {
74 self as u8
75 }
76
77 #[must_use]
79 pub const fn is_running(&self) -> bool {
80 matches!(self, Self::Running)
81 }
82}
83
84#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
89pub enum NodeRunMode {
90 #[default]
92 Owned,
93 Hosted,
95}
96
97impl NodeRunMode {
98 #[must_use]
100 pub const fn owns_signals(self) -> bool {
101 matches!(self, Self::Owned)
102 }
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub(super) enum RunningTransition {
107 Entered,
108 StopRequested,
109 Invalid(u8),
110}
111
112#[derive(Clone, Debug)]
117pub struct LiveNodeHandle {
118 control: Arc<AtomicU8>,
119 pub(crate) metrics: Arc<RunnerMetrics>,
120}
121
122impl Default for LiveNodeHandle {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128impl LiveNodeHandle {
129 #[must_use]
131 pub fn new() -> Self {
132 Self {
133 control: Arc::new(AtomicU8::new(NodeState::Idle.as_u8())),
134 metrics: Arc::new(RunnerMetrics::default()),
135 }
136 }
137
138 pub(crate) fn set_starting(&self) {
139 self.set_state(NodeState::Starting);
140 }
141
142 pub(crate) fn set_shutting_down(&self) {
143 self.set_state(NodeState::ShuttingDown);
144 }
145
146 pub(crate) fn set_stopped(&self) {
147 self.set_state(NodeState::Stopped);
148 }
149
150 pub(super) fn try_set_running(&self) -> RunningTransition {
151 match self.control.compare_exchange(
152 NodeState::Starting.as_u8(),
153 NodeState::Running.as_u8(),
154 Ordering::AcqRel,
155 Ordering::Acquire,
156 ) {
157 Ok(_) => RunningTransition::Entered,
158 Err(control) if control == (NodeState::Starting.as_u8() | STOP_REQUESTED) => {
159 RunningTransition::StopRequested
160 }
161 Err(control) => RunningTransition::Invalid(control),
162 }
163 }
164
165 fn set_state(&self, state: NodeState) {
166 let _ = self
167 .control
168 .try_update(Ordering::AcqRel, Ordering::Acquire, |control| {
169 Some((control & STOP_REQUESTED) | state.as_u8())
170 });
171 }
172
173 #[must_use]
175 pub fn state(&self) -> NodeState {
176 NodeState::from_u8(self.control.load(Ordering::Acquire) & STATE_MASK)
177 }
178
179 #[must_use]
181 pub fn should_stop(&self) -> bool {
182 self.control.load(Ordering::Acquire) & STOP_REQUESTED != 0
183 }
184
185 #[must_use]
187 pub fn is_running(&self) -> bool {
188 self.state().is_running()
189 }
190
191 #[must_use]
193 pub fn metrics_snapshot(&self) -> RunnerMetricsSnapshot {
194 self.metrics.snapshot()
195 }
196
197 pub fn stop(&self) {
199 self.control.fetch_or(STOP_REQUESTED, Ordering::AcqRel);
200 }
201}
202
203#[derive(Clone, Copy, Debug, PartialEq, Eq)]
204pub(super) enum EngineConnectionStatus {
205 Connected,
206 TimedOut,
207 StopRequested,
208 ShutdownRequested,
209}
210
211impl EngineConnectionStatus {
212 pub(super) const fn abort_reason(self) -> Option<&'static str> {
213 match self {
214 Self::Connected | Self::TimedOut => None,
215 Self::StopRequested => Some("Stop signal received during startup"),
216 Self::ShutdownRequested => Some("Shutdown signal received during startup"),
217 }
218 }
219}