aquarium_control/utilities/
signal_handler.rs

1/* Copyright 2024 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*/
9
10#[cfg(any(feature = "debug_signal_handler", feature = "debug_channels"))]
11use log::debug;
12use log::error;
13#[cfg(any(target_os = "linux", feature = "debug_signal_handler", not(test)))]
14use log::info;
15
16#[cfg(target_os = "linux")]
17use nix::unistd::gettid;
18
19use crate::launch::channels::{AquaReceiver, AquaSender};
20use crate::launch::execution_config::ExecutionConfig;
21use crate::utilities::channel_content::InternalCommand;
22pub(crate) use crate::utilities::signal_handler_channels::SignalHandlerChannels;
23use spin_sleep::SpinSleeper;
24use std::sync::atomic::{AtomicBool, Ordering};
25use std::{sync::Arc, time::Duration};
26
27/// Sends a specified `InternalCommand` to a target thread's channel and optionally waits for an acknowledgment.
28///
29/// The signal handler uses this helper function to communicate termination
30/// or other control commands to various application threads. It attempts to send
31/// the command and, if a receiver channel is provided, waits for a boolean acknowledgment
32/// to confirm the command was processed.
33///
34/// # Arguments
35/// * `command` - The `InternalCommand` to be sent to the target thread.
36/// * `tx_to_thread_opt` - An `Option` containing the sender part of the channel
37///   for communication to the target thread. If `None`, no command is sent.
38/// * `rx_from_thread_opt` - An `Option` containing the receiver part of the channel
39///   for communication *from* the target thread, used to receive an acknowledgment.
40///   If `None`, no acknowledgment is expected or waited for.
41/// * `thread_name` - The name of the target thread (for logging and debugging purposes).
42fn send_command(
43    command: InternalCommand,
44    tx_to_thread_opt: Option<&mut AquaSender<InternalCommand>>,
45    rx_from_thread_opt: Option<&mut AquaReceiver<bool>>,
46    thread_name: &str,
47) {
48    match tx_to_thread_opt {
49        Some(tx_to_thread) => {
50            match tx_to_thread.send(command) {
51                Ok(_) => {
52                    #[cfg(feature = "debug_signal_handler")]
53                    debug!(target: module_path!(), "QUIT sent to {}", thread_name);
54
55                    match rx_from_thread_opt {
56                        Some(rx_from_thread) => match rx_from_thread.recv() {
57                            Ok(_) => {
58                                #[cfg(feature = "debug_signal_handler")]
59                                debug!(target: module_path!(), "received acknowledgement from {}", thread_name);
60                            }
61                            Err(e) => {
62                                error!(
63                                    target: module_path!(),
64                                    "could not receive answer from {thread_name} thread {e:?}"
65                                );
66                            }
67                        },
68                        None => { /* do nothing */ }
69                    };
70                }
71                Err(e) => {
72                    error!(
73                        target: module_path!(),
74                        "channel communication to {thread_name} thread failed when sending Quit command ({e:?})"
75                    );
76                }
77            }
78        }
79        None => {
80            /* do nothing */
81            #[cfg(feature = "debug_signal_handler")]
82            {
83                let message = format!("not sending QUIT to {thread_name}");
84                debug!(target: module_path!(), "{message}");
85            }
86        }
87    }
88}
89
90/// Handles operating system termination signals (e.g., SIGTERM).
91///
92/// Upon receiving a termination signal, this function orchestrates a **safe, multiphase shutdown**
93/// of all application threads. It sends specific `InternalCommand` messages (`Quit` then `Terminate`)
94/// to each module in a predetermined order, ensuring dependencies are gracefully
95/// shut down before their consumers.
96///
97/// # Arguments
98/// * `term` - An atomic boolean flag that becomes `true` when a termination signal is received
99///   from the operating system. The function continuously monitors this flag.
100/// * `signal_handler_channels` - A struct containing all necessary `mpsc` sender and receiver
101///   channel pairs for communicating with different application threads and modules.
102///
103/// # Panics
104/// * If `signal_handler_channels` contains an inconsistent setup for TCP communication
105///   (e.g., a sender but no receiver, or vice versa).
106/// * If the function fails to register a hook for the OS termination signals.
107///
108/// # Thread Communication Flow
109/// The controlled shutdown sequence is visualized below:
110#[cfg_attr(doc, aquamarine::aquamarine)]
111/// ```mermaid
112/// graph LR
113///     signal_handler[Signal handler] --> messaging[Messaging]
114///     signal_handler --> refill[Refill control]
115///     refill --> signal_handler
116///     signal_handler --> tank_level_switch[Tank Level Switch]
117///     tank_level_switch --> signal_handler
118///     signal_handler --> heating[Heating control]
119///     heating --> signal_handler
120///     signal_handler --> relay_manager[Relay Manager]
121///     relay_manager --> signal_handler
122///     signal_handler --> atlas_scientific[Atlas Scientific]
123///     atlas_scientific --> signal_handler
124///     signal_handler --> ambient[Ambient]
125///     ambient --> signal_handler
126///     signal_handler --> data_logger[Data logger]
127///     data_logger --> signal_handler
128///     signal_handler --> balling[Balling dosing control]
129///     balling --> signal_handler
130///     signal_handler --> ventilation[Ventilation control]
131///     ventilation --> signal_handler
132///     signal_handler --> monitors[Monitors]
133///     monitors --> signal_handler
134///     signal_handler --> feed[Feed control]
135///     feed --> signal_handler
136///     schedule_check[ScheduleCheck] --> signal_handler
137///     signal_handler --> schedule_check
138///     signal_handler --> tcp_communication[TCP communication]
139///     tcp_communication --> signal_handler
140///     signal_handler --> dht[Dht]
141///     signal_handler --> i2c_interface[I2C interface]
142///     signal_handler --> watchdog[Watchdog]
143///     watchdog --> signal_handler
144/// ```
145pub fn handle_signals(
146    term: Arc<AtomicBool>,
147    mut signal_handler_channels: SignalHandlerChannels,
148    execution_config: ExecutionConfig,
149) {
150    #[cfg(target_os = "linux")]
151    info!(target: module_path!(), "Thread started with TID: {}", gettid());
152
153    if signal_handler_channels
154        .tx_signal_handler_to_tcp_opt
155        .is_some()
156        != signal_handler_channels
157            .rx_signal_handler_from_tcp_opt
158            .is_some()
159    {
160        panic!(
161            "{}: inconsistent channel parameters for TcpCommunication provided",
162            module_path!()
163        );
164    }
165    let use_simulator = signal_handler_channels
166        .tx_signal_handler_to_tcp_opt
167        .is_some();
168
169    let spin_sleeper = SpinSleeper::default();
170    let sleep_interval_millis = 100;
171    let sleep_duration_hundred_milli_sec = Duration::from_millis(sleep_interval_millis);
172
173    for signal in signal_hook::consts::TERM_SIGNALS {
174        match signal_hook::flag::register(*signal, Arc::clone(&term)) {
175            Ok(_) => {
176                #[cfg(feature = "debug_signal_handler")]
177                info!(
178                    target: module_path!(),
179                    "registered hook for signal {}",
180                    signal
181                );
182            }
183            Err(e) => {
184                panic!("{}: Could not hook SIGTERM routine: {e:?}", module_path!());
185            }
186        }
187    }
188
189    while !term.load(Ordering::Relaxed) {
190        spin_sleeper.sleep(sleep_duration_hundred_milli_sec);
191    }
192    #[cfg(not(test))]
193    info!(
194        target: module_path!(),
195        "received termination signal from operating system",
196    );
197
198    // phase 1: shut down highest level controls: data_logger
199    // data logger initiates communication to heating, refill, atlas_scientific, ambient
200    if execution_config.data_logger {
201        send_command(
202            InternalCommand::Quit,
203            Some(&mut signal_handler_channels.tx_signal_handler_to_data_logger),
204            Some(&mut signal_handler_channels.rx_signal_handler_from_data_logger),
205            "data_logger",
206        );
207    }
208    send_command(
209        InternalCommand::Quit,
210        signal_handler_channels
211            .tx_signal_handler_to_messaging_opt
212            .as_mut(),
213        signal_handler_channels
214            .rx_signal_handler_from_messaging_opt
215            .as_mut(),
216        "messaging",
217    );
218    #[cfg(feature = "debug_signal_handler")]
219    debug!(
220        target: module_path!(),
221        "phase 1 of termination concluded",
222    );
223
224    // phase 2: shut down high-level controls: heating, ventilation, feed, refill, balling
225    // these controls access the Controllino, Atlas Scientific and Dht
226    if execution_config.refill {
227        send_command(
228            InternalCommand::Quit,
229            Some(&mut signal_handler_channels.tx_signal_handler_to_refill),
230            Some(&mut signal_handler_channels.rx_signal_handler_from_refill),
231            "refill",
232        );
233    }
234    if execution_config.heating {
235        send_command(
236            InternalCommand::Quit,
237            Some(&mut signal_handler_channels.tx_signal_handler_to_heating),
238            Some(&mut signal_handler_channels.rx_signal_handler_from_heating),
239            "heating",
240        );
241    }
242    if execution_config.ventilation {
243        send_command(
244            InternalCommand::Quit,
245            Some(&mut signal_handler_channels.tx_signal_handler_to_ventilation),
246            Some(&mut signal_handler_channels.rx_signal_handler_from_ventilation),
247            "ventilation",
248        );
249    }
250    if execution_config.feed {
251        send_command(
252            InternalCommand::Quit,
253            Some(&mut signal_handler_channels.tx_signal_handler_to_feed),
254            Some(&mut signal_handler_channels.rx_signal_handler_from_feed),
255            "feed",
256        );
257    }
258    if execution_config.balling {
259        send_command(
260            InternalCommand::Quit,
261            Some(&mut signal_handler_channels.tx_signal_handler_to_balling),
262            Some(&mut signal_handler_channels.rx_signal_handler_from_balling),
263            "balling",
264        );
265    }
266    #[cfg(feature = "debug_signal_handler")]
267    debug!(
268        target: module_path!(),
269        "phase 2 of termination concluded",
270    );
271
272    // phase 3: shut down medium level controls
273    // these controls receive commands from upper level controls and communicate to i2c_interface and dht
274    if execution_config.tank_level_switch {
275        send_command(
276            InternalCommand::Quit,
277            Some(&mut signal_handler_channels.tx_signal_handler_to_tank_level_switch),
278            Some(&mut signal_handler_channels.rx_signal_handler_from_tank_level_switch),
279            "tank_level_switch",
280        );
281    }
282    if !use_simulator && execution_config.atlas_scientific {
283        send_command(
284            InternalCommand::Quit,
285            Some(&mut signal_handler_channels.tx_signal_handler_to_atlas_scientific),
286            Some(&mut signal_handler_channels.rx_signal_handler_from_atlas_scientific),
287            "atlas_scientific",
288        );
289    }
290    if execution_config.sensor_manager {
291        send_command(
292            InternalCommand::Quit,
293            Some(&mut signal_handler_channels.tx_signal_handler_to_sensor_manager),
294            Some(&mut signal_handler_channels.rx_signal_handler_from_sensor_manager),
295            "ambient",
296        );
297    }
298    if execution_config.schedule_check {
299        send_command(
300            InternalCommand::Quit,
301            Some(&mut signal_handler_channels.tx_signal_handler_to_schedule_check),
302            Some(&mut signal_handler_channels.rx_signal_handler_from_schedule_check),
303            "schedule_check",
304        );
305    }
306    #[cfg(feature = "debug_signal_handler")]
307    debug!(
308        target: module_path!(),
309        "phase 3 of termination concluded",
310    );
311
312    // phase 4: shut down low-level controls
313    #[cfg(all(feature = "target_hw", target_os = "linux"))]
314    {
315        if execution_config.i2c_interface {
316            send_command(
317                InternalCommand::Quit,
318                Some(&mut signal_handler_channels.tx_signal_handler_to_i2c_interface),
319                None,
320                "i2c_interface",
321            );
322        }
323        if execution_config.dht {
324            send_command(
325                InternalCommand::Quit,
326                Some(&mut signal_handler_channels.tx_signal_handler_to_dht),
327                None,
328                "dht",
329            );
330        }
331    }
332    if execution_config.relay_manager {
333        send_command(
334            InternalCommand::Quit,
335            Some(&mut signal_handler_channels.tx_signal_handler_to_relay_manager),
336            Some(&mut signal_handler_channels.rx_signal_handler_from_relay_manager),
337            "relay_manager",
338        );
339    }
340    send_command(
341        InternalCommand::Quit,
342        signal_handler_channels
343            .tx_signal_handler_to_tcp_opt
344            .as_mut(),
345        signal_handler_channels
346            .rx_signal_handler_from_tcp_opt
347            .as_mut(),
348        "tcp",
349    );
350    if execution_config.monitors {
351        send_command(
352            InternalCommand::Quit,
353            Some(&mut signal_handler_channels.tx_signal_handler_to_monitors),
354            Some(&mut signal_handler_channels.rx_signal_handler_from_monitors),
355            "monitors",
356        );
357    }
358    #[cfg(feature = "debug_signal_handler")]
359    debug!(
360        target: module_path!(),
361        "phase 4 of termination concluded",
362    );
363
364    // phase 5: send termination command to the highest level controls
365    if execution_config.data_logger {
366        send_command(
367            InternalCommand::Terminate,
368            Some(&mut signal_handler_channels.tx_signal_handler_to_data_logger),
369            None,
370            "data_logger",
371        );
372    }
373    #[cfg(feature = "debug_signal_handler")]
374    debug!(
375        target: module_path!(),
376        "phase 5 of termination concluded",
377    );
378
379    // phase 6: send termination command to high-level controls
380    if execution_config.heating {
381        send_command(
382            InternalCommand::Terminate,
383            Some(&mut signal_handler_channels.tx_signal_handler_to_heating),
384            None,
385            "heating",
386        );
387    }
388    if execution_config.ventilation {
389        send_command(
390            InternalCommand::Terminate,
391            Some(&mut signal_handler_channels.tx_signal_handler_to_ventilation),
392            None,
393            "ventilation",
394        );
395    }
396    if execution_config.feed {
397        send_command(
398            InternalCommand::Terminate,
399            Some(&mut signal_handler_channels.tx_signal_handler_to_feed),
400            None,
401            "feed",
402        );
403    }
404    if execution_config.refill {
405        send_command(
406            InternalCommand::Terminate,
407            Some(&mut signal_handler_channels.tx_signal_handler_to_refill),
408            None,
409            "refill",
410        );
411    }
412    if execution_config.balling {
413        send_command(
414            InternalCommand::Terminate,
415            Some(&mut signal_handler_channels.tx_signal_handler_to_balling),
416            None,
417            "balling",
418        );
419    }
420    #[cfg(feature = "debug_signal_handler")]
421    debug!(
422        target: module_path!(),
423        "phase 6 of termination concluded",
424    );
425
426    // phase 7: send quit/termination command to medium level controls
427    if execution_config.ds18b20 {
428        send_command(
429            InternalCommand::Quit,
430            Some(&mut signal_handler_channels.tx_signal_handler_to_ds18b20),
431            None,
432            "ds18b20",
433        );
434    }
435    #[cfg(all(feature = "target_hw", target_os = "linux"))]
436    {
437        if execution_config.atlas_scientific {
438            send_command(
439                InternalCommand::Terminate,
440                Some(&mut signal_handler_channels.tx_signal_handler_to_atlas_scientific),
441                None,
442                "atlas_scientific",
443            );
444        }
445    }
446
447    if execution_config.sensor_manager {
448        send_command(
449            InternalCommand::Terminate,
450            Some(&mut signal_handler_channels.tx_signal_handler_to_sensor_manager),
451            None,
452            "ambient",
453        );
454    }
455    if execution_config.tank_level_switch {
456        send_command(
457            InternalCommand::Terminate,
458            Some(&mut signal_handler_channels.tx_signal_handler_to_tank_level_switch),
459            None,
460            "tank_level_switch",
461        );
462    }
463    #[cfg(feature = "debug_signal_handler")]
464    debug!(
465        target: module_path!(),
466        "phase 7 of termination concluded",
467    );
468
469    // phase 8: allow messaging thread, memory and watchdog to close
470    if execution_config.watchdog {
471        send_command(
472            InternalCommand::Terminate,
473            Some(&mut signal_handler_channels.tx_signal_handler_to_watchdog),
474            Some(&mut signal_handler_channels.rx_signal_handler_from_watchdog),
475            "watchdog",
476        );
477    }
478    send_command(
479        InternalCommand::Terminate,
480        signal_handler_channels
481            .tx_signal_handler_to_messaging_opt
482            .as_mut(),
483        None,
484        "messaging",
485    );
486    send_command(
487        InternalCommand::Quit,
488        Some(&mut signal_handler_channels.tx_signal_handler_to_memory),
489        None,
490        "memory",
491    );
492
493    println!("Termination of application completed");
494
495    #[cfg(feature = "debug_channels")]
496    debug!(target: module_path!(), "{signal_handler_channels}");
497}
498
499#[cfg(test)]
500pub mod tests {
501    use crate::launch::channels::Channels;
502    use crate::launch::execution_config::ExecutionConfig;
503    use crate::utilities::channel_content::InternalCommand;
504    use crate::utilities::signal_handler::handle_signals;
505    use spin_sleep::SpinSleeper;
506    use std::sync::atomic::{AtomicBool, Ordering};
507    use std::time::Duration;
508    use std::{process::Command, sync::Arc, thread};
509
510    // helper function to send a signal to the application
511    fn send_signal(pid: u32, signal: i32) {
512        Command::new("kill")
513            .args(&[pid.to_string().as_str()])
514            .output()
515            .expect("Failed to send signal");
516        println!("Sent signal {} to {}", signal, pid);
517    }
518
519    // test if the signal handler sends shutdown requests to all threads in the right order.
520    #[test]
521    fn test_signal_handling() {
522        let term = Arc::new(AtomicBool::new(false));
523        let term_clone = Arc::clone(&term);
524
525        let mut channels = Channels::new_for_test();
526
527        // start the test object in a separate thread
528        let join_handle_test_object = thread::Builder::new()
529            .name("test_object".to_string())
530            .spawn(move || {
531                handle_signals(
532                    term_clone,
533                    channels.signal_handler,
534                    ExecutionConfig::default(),
535                );
536            })
537            .unwrap();
538
539        // use pid of the current process
540        let pid = std::process::id();
541
542        let spin_sleeper = SpinSleeper::default();
543        let sleep_duration_one_second = Duration::from_secs(1);
544
545        spin_sleeper.sleep(sleep_duration_one_second);
546
547        send_signal(pid, signal_hook::consts::signal::SIGABRT);
548
549        assert_eq!(
550            channels
551                .data_logger
552                .rx_data_logger_from_signal_handler
553                .recv()
554                .unwrap(),
555            InternalCommand::Quit
556        );
557        _ = channels
558            .data_logger
559            .tx_data_logger_to_signal_handler
560            .send(true);
561
562        assert_eq!(
563            channels
564                .messaging
565                .rx_messaging_from_signal_handler
566                .recv()
567                .unwrap(),
568            InternalCommand::Quit
569        );
570        _ = channels.messaging.tx_messaging_to_signal_handler.send(true);
571
572        assert_eq!(
573            channels
574                .refill
575                .rx_refill_from_signal_handler
576                .recv()
577                .unwrap(),
578            InternalCommand::Quit
579        );
580        _ = channels.refill.tx_refill_to_signal_handler.send(true);
581
582        assert_eq!(
583            channels
584                .heating
585                .rx_heating_from_signal_handler
586                .recv()
587                .unwrap(),
588            InternalCommand::Quit
589        );
590        _ = channels.heating.tx_heating_to_signal_handler.send(true);
591
592        assert_eq!(
593            channels
594                .ventilation
595                .rx_ventilation_from_signal_handler
596                .recv()
597                .unwrap(),
598            InternalCommand::Quit
599        );
600        _ = channels
601            .ventilation
602            .tx_ventilation_to_signal_handler
603            .send(true);
604
605        assert_eq!(
606            channels.feed.rx_feed_from_signal_handler.recv().unwrap(),
607            InternalCommand::Quit
608        );
609        _ = channels.feed.tx_feed_to_signal_handler.send(true);
610
611        assert_eq!(
612            channels
613                .balling
614                .rx_balling_from_signal_handler
615                .recv()
616                .unwrap(),
617            InternalCommand::Quit
618        );
619        _ = channels.balling.tx_balling_to_signal_handler.send(true);
620
621        assert_eq!(
622            channels
623                .tank_level_switch
624                .rx_tank_level_switch_from_signal_handler
625                .recv()
626                .unwrap(),
627            InternalCommand::Quit
628        );
629        _ = channels
630            .tank_level_switch
631            .tx_tank_level_switch_to_signal_handler
632            .send(true);
633        #[cfg(all(feature = "target_hw", target_os = "linux"))]
634        {
635            assert_eq!(
636                channels
637                    .atlas_scientific
638                    .rx_atlas_scientific_from_signal_handler
639                    .recv()
640                    .unwrap(),
641                InternalCommand::Quit
642            );
643            _ = channels
644                .atlas_scientific
645                .tx_atlas_scientific_to_signal_handler
646                .send(true);
647        }
648        assert_eq!(
649            channels
650                .sensor_manager
651                .rx_sensor_manager_from_signal_handler
652                .recv()
653                .unwrap(),
654            InternalCommand::Quit
655        );
656        _ = channels
657            .sensor_manager
658            .tx_sensor_manager_to_signal_handler
659            .send(true);
660        assert_eq!(
661            channels
662                .schedule_check
663                .rx_schedule_check_from_signal_handler
664                .recv()
665                .unwrap(),
666            InternalCommand::Quit
667        );
668        _ = channels
669            .schedule_check
670            .tx_schedule_check_to_signal_handler
671            .send(true);
672        #[cfg(all(target_os = "linux", feature = "target_hw"))]
673        assert_eq!(
674            channels
675                .i2c_interface
676                .rx_i2c_interface_from_signal_handler
677                .recv()
678                .unwrap(),
679            InternalCommand::Quit
680        );
681        // no response from i2c_interface
682
683        #[cfg(all(target_os = "linux", feature = "target_hw"))]
684        assert_eq!(
685            channels.dht.rx_dht_from_signal_handler.recv().unwrap(),
686            InternalCommand::Quit
687        );
688        // no response from dht
689
690        assert_eq!(
691            channels
692                .relay_manager
693                .rx_relay_manager_from_signal_handler
694                .recv()
695                .unwrap(),
696            InternalCommand::Quit
697        );
698        _ = channels
699            .relay_manager
700            .tx_relay_manager_to_signal_handler
701            .send(true);
702
703        assert_eq!(
704            channels
705                .tcp_communication
706                .rx_tcp_communication_from_signal_handler
707                .recv()
708                .unwrap(),
709            InternalCommand::Quit
710        );
711        _ = channels
712            .tcp_communication
713            .tx_tcp_communication_to_signal_handler
714            .send(true);
715
716        assert_eq!(
717            channels
718                .monitors
719                .rx_monitors_from_signal_handler
720                .recv()
721                .unwrap(),
722            InternalCommand::Quit
723        );
724        _ = channels.monitors.tx_monitors_to_signal_handler.send(true);
725
726        assert_eq!(
727            channels
728                .data_logger
729                .rx_data_logger_from_signal_handler
730                .recv()
731                .unwrap(),
732            InternalCommand::Terminate
733        );
734        // no response to termination request
735
736        assert_eq!(
737            channels
738                .heating
739                .rx_heating_from_signal_handler
740                .recv()
741                .unwrap(),
742            InternalCommand::Terminate
743        );
744        // no response to termination request
745
746        assert_eq!(
747            channels
748                .ventilation
749                .rx_ventilation_from_signal_handler
750                .recv()
751                .unwrap(),
752            InternalCommand::Terminate
753        );
754        // no response to termination request
755
756        assert_eq!(
757            channels.feed.rx_feed_from_signal_handler.recv().unwrap(),
758            InternalCommand::Terminate
759        );
760        // no response to termination request
761
762        assert_eq!(
763            channels
764                .refill
765                .rx_refill_from_signal_handler
766                .recv()
767                .unwrap(),
768            InternalCommand::Terminate
769        );
770        // no response to termination request
771
772        assert_eq!(
773            channels
774                .balling
775                .rx_balling_from_signal_handler
776                .recv()
777                .unwrap(),
778            InternalCommand::Terminate
779        );
780        // no response to termination request
781
782        assert_eq!(
783            channels
784                .ds18b20
785                .rx_ds18b20_from_signal_handler
786                .recv()
787                .unwrap(),
788            InternalCommand::Quit
789        );
790        // no response to quit signal from ds18b20
791
792        #[cfg(all(feature = "target_hw", target_os = "linux"))]
793        {
794            assert_eq!(
795                channels
796                    .atlas_scientific
797                    .rx_atlas_scientific_from_signal_handler
798                    .recv()
799                    .unwrap(),
800                InternalCommand::Terminate
801            );
802        }
803        // no response to termination request
804
805        assert_eq!(
806            channels
807                .sensor_manager
808                .rx_sensor_manager_from_signal_handler
809                .recv()
810                .unwrap(),
811            InternalCommand::Terminate
812        );
813        // no response to termination request
814
815        assert_eq!(
816            channels
817                .tank_level_switch
818                .rx_tank_level_switch_from_signal_handler
819                .recv()
820                .unwrap(),
821            InternalCommand::Terminate
822        );
823        // no response to termination request
824
825        assert_eq!(
826            channels
827                .watchdog
828                .rx_watchdog_from_signal_handler
829                .recv()
830                .unwrap(),
831            InternalCommand::Terminate
832        );
833        _ = channels.watchdog.tx_watchdog_to_signal_handler.send(true);
834
835        assert_eq!(
836            channels
837                .messaging
838                .rx_messaging_from_signal_handler
839                .recv()
840                .unwrap(),
841            InternalCommand::Terminate
842        );
843        // no response to termination request
844
845        join_handle_test_object
846            .join()
847            .expect("Test object thread did not finish.");
848
849        assert!(term.load(Ordering::Acquire));
850    }
851}