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}