aquarium_control/utilities/
check_mutex_access_duration.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::sensors::atlas_scientific::AtlasScientific;
10use crate::sensors::dht::Dht;
11use crate::sensors::ds18b20::Ds18b20;
12use crate::sensors::sensor_manager::SensorManager;
13use crate::sensors::tank_level_switch::TankLevelSwitch;
14use crate::thermal::heating::Heating;
15use crate::thermal::ventilation::Ventilation;
16use crate::utilities::channel_content::AquariumSignal;
17use crate::water::water_injection::WaterInjection;
18#[cfg(not(test))]
19use log::warn;
20use std::time::{Duration, Instant};
21
22pub trait CheckMutexAccessDurationTrait {
23    /// Reads the warn-lock.
24    /// This flag is used to prevent log-flooding
25    fn get_warn_lock(&self, key_opt: &Option<&AquariumSignal>) -> bool;
26
27    /// Sets the warn-lock.
28    /// This flag is used to prevent log-flooding
29    fn set_warn_lock(&mut self, key: &Option<&AquariumSignal>, value: bool);
30
31    /// Records violation of mutex access duration for testing purposes
32    #[cfg(test)]
33    fn record_access_duration_violation(&mut self, key_opt: &Option<&AquariumSignal>);
34
35    /// Provides the maximum permissible access duration
36    fn get_max_mutex_access_duration(&self) -> Duration;
37
38    /// Inform the originator for logging purposes
39    #[cfg(not(test))]
40    fn get_location(&self) -> &str;
41
42    fn check_mutex_access_duration(
43        &mut self,
44        key_opt: Option<&AquariumSignal>,
45        instant_after_locking_mutex: Instant,
46        instant_before_locking_mutex: Instant,
47    ) {
48        // check if access to mutex took too long
49        let measured_mutex_access_duration =
50            instant_after_locking_mutex.duration_since(instant_before_locking_mutex);
51        let warn_lock = self.get_warn_lock(&key_opt);
52        if measured_mutex_access_duration > self.get_max_mutex_access_duration() {
53            if !warn_lock {
54                self.set_warn_lock(&key_opt, true);
55                #[cfg(not(test))]
56                warn!(target: self.get_location(),
57                        "Access to mutex took too long. Allowed={}ms, Measured={}ms",
58                        self.get_max_mutex_access_duration().as_millis(),
59                        measured_mutex_access_duration.as_millis(),
60                );
61                // record for testing purposes only
62                cfg_if::cfg_if! {
63                    if #[cfg(test)] {
64                        self.record_access_duration_violation(&key_opt);
65                    }
66                }
67            }
68        } else {
69            self.set_warn_lock(&key_opt, false);
70        }
71    }
72}
73
74impl CheckMutexAccessDurationTrait for TankLevelSwitch {
75    /// Method connects the default trait implementation with the specific implementation
76    /// reading the warn-lock.
77    fn get_warn_lock(&self, _key_opt: &Option<&AquariumSignal>) -> bool {
78        self.lock_warn_max_mutex_access_duration
79    }
80
81    /// Method connects the default trait implementation with the specific implementation
82    /// setting the warn-lock.
83    fn set_warn_lock(&mut self, _key_opt: &Option<&AquariumSignal>, value: bool) {
84        self.lock_warn_max_mutex_access_duration = value;
85    }
86
87    #[cfg(test)]
88    /// Method connects the default trait implementation with the specific implementation
89    /// for recording access duration violation
90    fn record_access_duration_violation(&mut self, _key_opt: &Option<&AquariumSignal>) {
91        self.mutex_access_duration_exceeded = true;
92    }
93
94    /// Method connects the default trait implementation with the specific implementation
95    /// for getting maximum permissible access duration
96    fn get_max_mutex_access_duration(&self) -> Duration {
97        self.max_mutex_access_duration
98    }
99
100    /// inform the location of the warning for logging purposes
101    #[cfg(not(test))]
102    fn get_location(&self) -> &str {
103        "tank level switch"
104    }
105}
106
107impl CheckMutexAccessDurationTrait for Heating {
108    /// Method connects the default trait implementation with the specific implementation
109    /// reading the warn-lock.
110    fn get_warn_lock(&self, _key_opt: &Option<&AquariumSignal>) -> bool {
111        self.lock_warn_max_mutex_access_duration
112    }
113
114    /// Method connects the default trait implementation with the specific implementation
115    /// setting the warn-lock.
116    fn set_warn_lock(&mut self, _key_opt: &Option<&AquariumSignal>, value: bool) {
117        self.lock_warn_max_mutex_access_duration = value;
118    }
119
120    #[cfg(test)]
121    /// Method connects the default trait implementation with the specific implementation
122    /// for recording access duration violation
123    fn record_access_duration_violation(&mut self, _key_opt: &Option<&AquariumSignal>) {
124        self.mutex_access_duration_exceeded = true;
125    }
126
127    /// Method connects the default trait implementation with the specific implementation
128    /// for getting maximum permissible access duration
129    fn get_max_mutex_access_duration(&self) -> Duration {
130        self.max_mutex_access_duration
131    }
132
133    /// inform the location of the warning for logging purposes
134    #[cfg(not(test))]
135    fn get_location(&self) -> &str {
136        "heating"
137    }
138}
139
140impl CheckMutexAccessDurationTrait for Ventilation {
141    /// Method connects the default trait implementation with the specific implementation
142    /// reading the warn-lock.
143    fn get_warn_lock(&self, _key_opt: &Option<&AquariumSignal>) -> bool {
144        self.lock_warn_max_mutex_access_duration
145    }
146
147    /// Method connects the default trait implementation with the specific implementation
148    /// setting the warn-lock.
149    fn set_warn_lock(&mut self, _key_opt: &Option<&AquariumSignal>, value: bool) {
150        self.lock_warn_max_mutex_access_duration = value;
151    }
152
153    #[cfg(test)]
154    /// Method connects the default trait implementation with the specific implementation
155    /// for recording access duration violation
156    fn record_access_duration_violation(&mut self, _key_opt: &Option<&AquariumSignal>) {
157        self.mutex_access_duration_exceeded = true;
158    }
159
160    /// Method connects the default trait implementation with the specific implementation
161    /// for getting maximum permissible access duration
162    fn get_max_mutex_access_duration(&self) -> Duration {
163        self.max_mutex_access_duration
164    }
165
166    /// inform the location of the warning for logging purposes
167    #[cfg(not(test))]
168    fn get_location(&self) -> &str {
169        "ventilation"
170    }
171}
172
173impl CheckMutexAccessDurationTrait for Dht {
174    /// Method connects the default trait implementation with the specific implementation
175    /// reading the warn-lock.
176    fn get_warn_lock(&self, _key_opt: &Option<&AquariumSignal>) -> bool {
177        self.lock_warn_max_mutex_access_duration
178    }
179
180    /// Method connects the default trait implementation with the specific implementation
181    /// setting the warn-lock.
182    fn set_warn_lock(&mut self, _key_opt: &Option<&AquariumSignal>, value: bool) {
183        self.lock_warn_max_mutex_access_duration = value;
184    }
185
186    #[cfg(test)]
187    /// Method connects the default trait implementation with the specific implementation
188    /// for recording access duration violation
189    fn record_access_duration_violation(&mut self, _key_opt: &Option<&AquariumSignal>) {
190        self.mutex_access_duration_exceeded = true;
191    }
192
193    /// Method connects the default trait implementation with the specific implementation
194    /// for getting maximum permissible access duration
195    fn get_max_mutex_access_duration(&self) -> Duration {
196        self.max_mutex_access_duration
197    }
198
199    /// inform the location of the warning for logging purposes
200    #[cfg(not(test))]
201    fn get_location(&self) -> &str {
202        "dht"
203    }
204}
205
206impl CheckMutexAccessDurationTrait for SensorManager {
207    /// Method connects the default trait implementation with the specific implementation
208    /// reading the warn-lock.
209    fn get_warn_lock(&self, _key_opt: &Option<&AquariumSignal>) -> bool {
210        self.lock_warn_max_mutex_access_duration
211    }
212
213    /// Method connects the default trait implementation with the specific implementation
214    /// setting the warn-lock.
215    fn set_warn_lock(&mut self, _key_opt: &Option<&AquariumSignal>, value: bool) {
216        self.lock_warn_max_mutex_access_duration = value;
217    }
218
219    #[cfg(test)]
220    /// Method connects the default trait implementation with the specific implementation
221    /// for recording access duration violation
222    fn record_access_duration_violation(&mut self, _key_opt: &Option<&AquariumSignal>) {
223        self.mutex_access_duration_exceeded = true;
224    }
225
226    /// Method connects the default trait implementation with the specific implementation
227    /// for getting maximum permissible access duration
228    fn get_max_mutex_access_duration(&self) -> Duration {
229        self.max_mutex_access_duration
230    }
231
232    /// inform the location of the warning for logging purposes
233    #[cfg(not(test))]
234    fn get_location(&self) -> &str {
235        "dht"
236    }
237}
238
239impl CheckMutexAccessDurationTrait for WaterInjection {
240    /// Method connects the default trait implementation with the specific implementation
241    /// reading the warn-lock.
242    fn get_warn_lock(&self, _key_opt: &Option<&AquariumSignal>) -> bool {
243        self.lock_warn_max_mutex_access_duration
244    }
245
246    /// Method connects the default trait implementation with the specific implementation
247    /// setting the warn-lock.
248    fn set_warn_lock(&mut self, _key_opt: &Option<&AquariumSignal>, value: bool) {
249        self.lock_warn_max_mutex_access_duration = value;
250    }
251
252    #[cfg(test)]
253    /// Method connects the default trait implementation with the specific implementation
254    /// for recording access duration violation
255    fn record_access_duration_violation(&mut self, _key_opt: &Option<&AquariumSignal>) {
256        self.mutex_access_duration_exceeded = true;
257    }
258
259    /// Method connects the default trait implementation with the specific implementation
260    /// for getting maximum permissible access duration
261    fn get_max_mutex_access_duration(&self) -> Duration {
262        self.max_mutex_access_duration
263    }
264
265    /// inform the location of the warning for logging purposes
266    #[cfg(not(test))]
267    fn get_location(&self) -> &str {
268        "water_injection"
269    }
270}
271
272impl CheckMutexAccessDurationTrait for Ds18b20 {
273    /// Method connects the default trait implementation with the specific implementation
274    /// reading the warn-lock.
275    fn get_warn_lock(&self, _key_opt: &Option<&AquariumSignal>) -> bool {
276        self.lock_warn_max_mutex_access_duration
277    }
278
279    /// Method connects the default trait implementation with the specific implementation
280    /// setting the warn-lock.
281    fn set_warn_lock(&mut self, _key: &Option<&AquariumSignal>, value: bool) {
282        self.lock_warn_max_mutex_access_duration = value;
283    }
284
285    #[cfg(test)]
286    /// Method connects the default trait implementation with the specific implementation
287    /// for recording access duration violation
288    fn record_access_duration_violation(&mut self, _key_opt: &Option<&AquariumSignal>) {
289        self.mutex_access_duration_exceeded = true;
290    }
291
292    /// Method connects the default trait implementation with the specific implementation
293    /// for getting maximum permissible access duration
294    fn get_max_mutex_access_duration(&self) -> Duration {
295        self.max_mutex_access_duration
296    }
297
298    /// inform the location of the warning for logging purposes
299    #[cfg(not(test))]
300    fn get_location(&self) -> &str {
301        "ventilation"
302    }
303}
304impl CheckMutexAccessDurationTrait for AtlasScientific {
305    fn get_warn_lock(&self, key_opt: &Option<&AquariumSignal>) -> bool {
306        if key_opt.is_some() {
307            match key_opt.as_ref().unwrap() {
308                AquariumSignal::WaterTemperature => {
309                    self.lock_warn_max_mutex_access_duration_temperature
310                }
311                AquariumSignal::pH => self.lock_warn_max_mutex_access_duration_ph,
312                AquariumSignal::Conductivity => {
313                    self.lock_warn_max_mutex_access_duration_conductivity
314                }
315                _ => false, // Default for unhandled signals
316            }
317        } else {
318            // defensive programming: this case should not occur
319            true
320        }
321    }
322
323    fn set_warn_lock(&mut self, key_opt: &Option<&AquariumSignal>, value: bool) {
324        if key_opt.is_some() {
325            match key_opt.as_ref().unwrap() {
326                AquariumSignal::WaterTemperature => {
327                    self.lock_warn_max_mutex_access_duration_temperature = value
328                }
329                AquariumSignal::pH => self.lock_warn_max_mutex_access_duration_ph = value,
330                AquariumSignal::Conductivity => {
331                    self.lock_warn_max_mutex_access_duration_conductivity = value
332                }
333                _ => {} // Do nothing for unhandled signals
334            }
335        }
336    }
337
338    #[cfg(test)]
339    /// Method connects the default trait implementation with the specific implementation
340    /// for recording access duration violation
341    #[cfg(test)]
342    fn record_access_duration_violation(&mut self, key_opt: &Option<&AquariumSignal>) {
343        if key_opt.is_some() {
344            match key_opt.as_ref().unwrap() {
345                AquariumSignal::WaterTemperature => {
346                    self.mutex_temperature_access_duration_exceeded = true
347                }
348                AquariumSignal::pH => self.mutex_ph_access_duration_exceeded = true,
349                AquariumSignal::Conductivity => {
350                    self.mutex_conductivity_access_duration_exceeded = true
351                }
352                _ => { /* do nothing for unrecognized signal */ }
353            }
354        }
355    }
356
357    /// Method connects the default trait implementation with the specific implementation
358    /// for getting maximum permissible access duration
359    fn get_max_mutex_access_duration(&self) -> Duration {
360        self.max_mutex_access_duration_millis
361    }
362
363    /// inform the location of the warning for logging purposes
364    #[cfg(not(test))]
365    fn get_location(&self) -> &str {
366        "atlas_scientific"
367    }
368}