aquarium_control/database/
pingable.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*/
9
10//! Defines a contract for components that permanently use a database connection.
11//!
12//! This module introduces the `Pingable` trait, which provides a generic interface
13//! for sending a lightweight "ping" to a database to prevent it from timing out
14//! due to inactivity.
15//!
16//! ## Key Components
17//!
18//! - **`Pingable` Trait**: A simple trait with a single method, `ping()`. It abstracts
19//!   the action of performing a keep-alive check. By using a trait, generic functions
20//!   and macros can operate on any database interface that implements this behavior
21//!   without needing to know its concrete type. This is heavily used by the
22//!   `check_quit_increment_counter_ping_database!` macro.
23//!
24//! - **Implementations**: The module provides `impl Pingable` blocks for various
25//!   `SqlInterface...` structs (`SqlInterfaceData`, `SqlInterfaceBalling`, etc.).
26//!   Each implementation calls a common `SqlInterface::ping_database` function,
27//!   ensuring consistent behavior across the application.
28//!
29//! ## Design and Purpose
30//!
31//! The primary goal is to create a uniform, "fire-and-forget" keep-alive mechanism.
32//! Long-running threads that interact with the database only intermittently can use
33//! this trait to ensure their connection remains active.
34//!
35//! The `ping` method is designed to be non-disruptive; it logs any errors internally
36//! but does not propagate them. This prevents keep-alive failures from crashing
37//! a main control loop.
38
39use crate::database::sql_interface::SqlInterface;
40use crate::database::sql_interface_balling::SqlInterfaceBalling;
41use crate::database::sql_interface_data::SqlInterfaceData;
42use crate::database::sql_interface_feed::SqlInterfaceFeed;
43use crate::database::sql_interface_heating_stats::SqlInterfaceHeatingStats;
44use crate::database::sql_interface_refill::SqlInterfaceRefill;
45use crate::database::sql_interface_schedule::SqlInterfaceSchedule;
46use crate::utilities::logger::log_error_chain;
47
48/// Represents a component that can be "pinged" to keep a connection alive.
49pub trait Pingable {
50    /// Pings the database to check connection health and prevent timeouts.
51    ///
52    /// This function executes a lightweight query to ensure the connection is still
53    /// active. If the ping fails, it logs an error using `log::error!` but does
54    /// not propagate the error to the caller. This is a "fire-and-forget"
55    /// keep-alive mechanism.
56    fn ping(&mut self);
57}
58
59/// This macro assumes that each struct provided has a `conn` field which is a
60/// mutable database connection that can be passed to `SqlInterface::ping_database`.
61macro_rules! impl_pingable {
62    // The `$(...)+` syntax allows the macro to accept one or more struct types,
63    // separated by commas.
64    ($($struct_name:ty),+) => {
65        $(
66            impl Pingable for $struct_name {
67                /// Pings the database to check connection health and prevent timeouts.
68                fn ping(&mut self) {
69                    if let Err(e) = SqlInterface::ping_database(&mut self.conn) {
70                        log_error_chain(
71                            module_path!(),
72                            "Encountered error while pinging database.",
73                            e,
74                        );
75                    }
76                }
77            }
78        )+
79    };
80}
81
82// Now, apply the implementation to all relevant structs in a single, clean call.
83impl_pingable!(
84    SqlInterfaceData,
85    SqlInterfaceBalling,
86    SqlInterfaceFeed,
87    SqlInterfaceHeatingStats,
88    SqlInterfaceRefill,
89    SqlInterfaceSchedule
90);