aquarium_control/utilities/wait_for_termination.rs
1/* Copyright 2025 Uwe Martin
2
3Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
5The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
7THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8*/
9use crate::food::feed::Feed;
10use crate::mineral::balling::Balling;
11use crate::recorder::data_logger::DataLogger;
12use crate::sensors::atlas_scientific::AtlasScientific;
13use crate::sensors::tank_level_switch::TankLevelSwitch;
14use crate::utilities::channel_content::InternalCommand;
15use log::{error, warn};
16use spin_sleep::SpinSleeper;
17use std::time::Duration;
18
19#[cfg(target_os = "linux")]
20use crate::dispatch::messaging::Messaging;
21use crate::launch::channels::{AquaChannelError, AquaReceiver};
22use crate::sensors::sensor_manager::SensorManager;
23use crate::thermal::heating::Heating;
24use crate::thermal::ventilation::Ventilation;
25use crate::water::refill::Refill;
26
27/// Defines a standard behavior for components that need to wait for a termination signal.
28///
29/// This trait is implemented by long-running components (often those running in their own thread)
30/// that need to perform a graceful shutdown. After completing their primary tasks, they can call
31/// `wait_for_termination` to pause execution efficiently until the central signal handler
32/// sends a final `InternalCommand::Terminate` command.
33pub trait WaitForTerminationTrait {
34 /// Provides a mutable reference to the component's warning lock flag.
35 /// This flag is used to prevent log-flooding with inapplicable command warnings.
36 fn get_warn_lock_mut(&mut self) -> &mut bool;
37
38 /// Provides a mutable reference to the component's error lock flag.
39 /// This flag is used to prevent log-flooding with the channel receive errors.
40 fn get_error_lock_mut(&mut self) -> &mut bool;
41
42 /// Implements the graceful shutdown wait loop for several threads (default implementation).
43 ///
44 /// This function enters a loop that continuously checks for a termination command from the
45 /// signal handler. It uses `try_recv` for non-blocking message checking.
46 ///
47 /// - If `InternalCommand::Terminate` is received, the loop exits.
48 /// - If any other `InternalCommand` is received, a warning is logged. A lock flag
49 /// (`lock_warn_inapplicable_command_signal_handler`) prevents spamming the log with
50 /// repeated warnings.
51 /// - If the channel receive operation fails (e.g., the sender is dropped), an error is
52 /// logged. A lock flag (`lock_error_channel_receive_termination`) prevents repeated
53 /// error logs.
54 ///
55 /// The thread sleeps for the specified `sleep_duration` between each check
56 /// to minimize CPU consumption.
57 ///
58 /// # Arguments
59 /// * `rx_waiting_thread_from_signal_handler` - The channel receiver to listen for commands.
60 /// * `sleep_duration` - The duration to pause between checks.
61 /// * `origin` - module name which called this function.
62 fn wait_for_termination(
63 &mut self,
64 rx_waiting_thread_from_signal_handler: &mut AquaReceiver<InternalCommand>,
65 sleep_duration: Duration,
66 origin: &str,
67 ) {
68 let spin_sleeper = SpinSleeper::default();
69 let mut termination_command_received = false;
70 while !termination_command_received {
71 termination_command_received = match rx_waiting_thread_from_signal_handler.try_recv() {
72 Ok(c) => match c {
73 InternalCommand::Terminate => {
74 *self.get_warn_lock_mut() = false;
75 true
76 }
77 _ => {
78 if !*self.get_warn_lock_mut() {
79 warn!(
80 target: origin,
81 "ignoring inapplicable command from signal handler ({c})"
82 );
83 *self.get_warn_lock_mut() = true;
84 }
85 false
86 }
87 },
88 Err(e) => {
89 match e {
90 #[cfg(feature = "debug_channels")]
91 AquaChannelError::Full => { /* Not applicable. Do nothing */ }
92 AquaChannelError::Empty => { /* do nothing */ }
93 AquaChannelError::Disconnected => {
94 if !*self.get_error_lock_mut() {
95 error!(
96 target: origin,
97 "receiving termination signal from signal handler failed ({e:?})"
98 );
99 *self.get_warn_lock_mut() = true;
100 }
101 }
102 }
103 false
104 }
105 };
106 spin_sleeper.sleep(sleep_duration);
107 }
108 }
109}
110
111impl WaitForTerminationTrait for TankLevelSwitch {
112 /// Method connects the default trait implementation with the specific implementation
113 /// accessing the warn-lock.
114 fn get_warn_lock_mut(&mut self) -> &mut bool {
115 &mut self.lock_warn_inapplicable_command_signal_handler
116 }
117
118 /// Method connects the default trait implementation with the specific implementation
119 /// for accessing the error-lock.
120 fn get_error_lock_mut(&mut self) -> &mut bool {
121 &mut self.lock_error_channel_receive_termination
122 }
123}
124
125impl WaitForTerminationTrait for Ventilation {
126 /// Method connects the default trait implementation with the specific implementation
127 /// accessing the warn-lock.
128 fn get_warn_lock_mut(&mut self) -> &mut bool {
129 &mut self.lock_warn_inapplicable_command_signal_handler
130 }
131
132 /// Method connects the default trait implementation with the specific implementation
133 /// for accessing the error-lock.
134 fn get_error_lock_mut(&mut self) -> &mut bool {
135 &mut self.lock_error_channel_receive_termination
136 }
137}
138
139impl WaitForTerminationTrait for Refill {
140 /// Method connects the default trait implementation with the specific implementation
141 /// accessing the warn-lock.
142 fn get_warn_lock_mut(&mut self) -> &mut bool {
143 &mut self.lock_warn_inapplicable_command_signal_handler
144 }
145
146 /// Method connects the default trait implementation with the specific implementation
147 /// for accessing the error-lock.
148 fn get_error_lock_mut(&mut self) -> &mut bool {
149 &mut self.lock_error_channel_receive_termination
150 }
151}
152
153impl WaitForTerminationTrait for Heating {
154 /// Method connects the default trait implementation with the specific implementation
155 /// accessing the warn-lock.
156 fn get_warn_lock_mut(&mut self) -> &mut bool {
157 &mut self.lock_warn_inapplicable_command_signal_handler
158 }
159
160 /// Method connects the default trait implementation with the specific implementation
161 /// for accessing the error-lock.
162 fn get_error_lock_mut(&mut self) -> &mut bool {
163 &mut self.lock_error_channel_receive_termination
164 }
165}
166
167impl WaitForTerminationTrait for Feed {
168 /// Method connects the default trait implementation with the specific implementation
169 /// accessing the warn-lock.
170 fn get_warn_lock_mut(&mut self) -> &mut bool {
171 &mut self.lock_warn_inapplicable_command_signal_handler
172 }
173
174 /// Method connects the default trait implementation with the specific implementation
175 /// for accessing the error-lock.
176 fn get_error_lock_mut(&mut self) -> &mut bool {
177 &mut self.lock_error_channel_receive_termination
178 }
179}
180
181impl WaitForTerminationTrait for Balling {
182 /// Method connects the default trait implementation with the specific implementation
183 /// accessing the warn-lock.
184 fn get_warn_lock_mut(&mut self) -> &mut bool {
185 &mut self.lock_warn_inapplicable_command_signal_handler
186 }
187
188 /// Method connects the default trait implementation with the specific implementation
189 /// for accessing the error-lock.
190 fn get_error_lock_mut(&mut self) -> &mut bool {
191 &mut self.lock_error_channel_receive_termination
192 }
193}
194
195impl WaitForTerminationTrait for SensorManager {
196 /// Method connects the default trait implementation with the specific implementation
197 /// accessing the warn-lock.
198 fn get_warn_lock_mut(&mut self) -> &mut bool {
199 &mut self.lock_warn_inapplicable_command_signal_handler
200 }
201
202 /// Method connects the default trait implementation with the specific implementation
203 /// for accessing the error-lock.
204 fn get_error_lock_mut(&mut self) -> &mut bool {
205 &mut self.lock_error_channel_receive_termination
206 }
207}
208
209impl WaitForTerminationTrait for AtlasScientific {
210 /// Method connects the default trait implementation with the specific implementation
211 /// accessing the warn-lock.
212 fn get_warn_lock_mut(&mut self) -> &mut bool {
213 &mut self.lock_warn_inapplicable_command_signal_handler
214 }
215
216 /// Method connects the default trait implementation with the specific implementation
217 /// for accessing the error-lock.
218 fn get_error_lock_mut(&mut self) -> &mut bool {
219 &mut self.lock_error_channel_receive_termination
220 }
221}
222
223#[cfg(target_os = "linux")]
224impl WaitForTerminationTrait for Messaging {
225 /// Method connects the default trait implementation with the specific implementation
226 /// accessing the warn-lock.
227 fn get_warn_lock_mut(&mut self) -> &mut bool {
228 &mut self.lock_warn_inapplicable_command_signal_handler
229 }
230
231 /// Method connects the default trait implementation with the specific implementation
232 /// for accessing the error-lock.
233 fn get_error_lock_mut(&mut self) -> &mut bool {
234 &mut self.lock_error_channel_receive_termination
235 }
236}
237
238impl WaitForTerminationTrait for DataLogger {
239 /// Method connects the default trait implementation with the specific implementation
240 /// accessing the warn-lock.
241 fn get_warn_lock_mut(&mut self) -> &mut bool {
242 &mut self.lock_warn_inapplicable_command_signal_handler
243 }
244
245 /// Method connects the default trait implementation with the specific implementation
246 /// for accessing the error-lock.
247 fn get_error_lock_mut(&mut self) -> &mut bool {
248 &mut self.lock_error_channel_receive_termination
249 }
250}