Skip to main content

sentinel_driver/pool/
health.rs

1use std::time::Instant;
2
3use crate::connection::stream::PgConnection;
4use crate::protocol::backend::BackendMessage;
5use crate::protocol::frontend;
6
7/// Check if a connection is still alive by sending an empty query.
8///
9/// Sends `""` via simple query protocol. The server responds with
10/// `EmptyQueryResponse` + `ReadyForQuery`. Returns `false` on any error.
11/// Cost: ~50us round-trip.
12pub(crate) async fn check_alive(conn: &mut PgConnection) -> bool {
13    frontend::query(conn.write_buf(), "");
14    try_check_alive(conn).await.unwrap_or(false)
15}
16
17/// Inner function that uses `?` for unified error handling.
18async fn try_check_alive(conn: &mut PgConnection) -> crate::error::Result<bool> {
19    conn.send().await?;
20
21    // Drain until ReadyForQuery
22    loop {
23        if matches!(conn.recv().await?, BackendMessage::ReadyForQuery { .. }) {
24            return Ok(true);
25        }
26    }
27}
28
29/// Strategy for checking connection health.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[non_exhaustive]
32pub enum HealthCheckStrategy {
33    /// Flag-based check — no query, just check if the connection has
34    /// been marked as broken by a previous I/O error. Fastest option (<0.5μs).
35    Fast,
36    /// Send `SELECT 1` to verify the connection is alive.
37    /// More reliable but adds ~100μs per checkout.
38    Query,
39    /// No health check — assume connections are always valid.
40    /// Use only in controlled environments.
41    None,
42}
43
44/// Metadata for a pooled connection — used for idle timeout and max lifetime.
45#[derive(Debug)]
46pub struct ConnectionMeta {
47    /// When this connection was created.
48    pub created_at: Instant,
49    /// When this connection was last returned to the pool.
50    pub last_used: Instant,
51    /// Flag set when an I/O error occurs — marks connection as broken.
52    pub is_broken: bool,
53}
54
55impl Default for ConnectionMeta {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl ConnectionMeta {
62    pub fn new() -> Self {
63        let now = Instant::now();
64        Self {
65            created_at: now,
66            last_used: now,
67            is_broken: false,
68        }
69    }
70
71    /// Mark this connection as recently used (returned to pool).
72    pub fn touch(&mut self) {
73        self.last_used = Instant::now();
74    }
75
76    /// Check if the connection has exceeded the idle timeout.
77    pub fn is_idle_expired(&self, timeout: std::time::Duration) -> bool {
78        self.last_used.elapsed() > timeout
79    }
80
81    /// Check if the connection has exceeded its max lifetime.
82    pub fn is_lifetime_expired(&self, max_lifetime: std::time::Duration) -> bool {
83        self.created_at.elapsed() > max_lifetime
84    }
85}