Skip to main content

sentinel_driver/
advisory_lock.rs

1use std::hash::{Hash, Hasher};
2
3use crate::error::Result;
4use crate::Connection;
5
6/// A PostgreSQL advisory lock identifier.
7///
8/// Advisory locks are application-level locks that don't lock any table or row.
9/// They are useful for coordinating access to external resources.
10///
11/// # Example
12///
13/// ```rust,no_run
14/// # async fn example(conn: &mut sentinel_driver::Connection) -> sentinel_driver::Result<()> {
15/// use sentinel_driver::advisory_lock::PgAdvisoryLock;
16///
17/// let lock = PgAdvisoryLock::new(12345);
18/// let guard = lock.acquire(conn).await?;
19/// // ... do work under lock ...
20/// guard.release(conn).await?;
21/// # Ok(())
22/// # }
23/// ```
24#[derive(Debug, Clone, Copy)]
25pub struct PgAdvisoryLock {
26    key: i64,
27}
28
29impl PgAdvisoryLock {
30    /// Create an advisory lock from a numeric key.
31    pub fn new(key: i64) -> Self {
32        Self { key }
33    }
34
35    /// Create an advisory lock from a string key.
36    ///
37    /// The string is hashed to produce a stable i64 key using the
38    /// default hasher.
39    pub fn from_name(name: &str) -> Self {
40        let mut hasher = std::collections::hash_map::DefaultHasher::new();
41        name.hash(&mut hasher);
42        Self {
43            key: hasher.finish() as i64,
44        }
45    }
46
47    /// The numeric key for this lock.
48    pub fn key(&self) -> i64 {
49        self.key
50    }
51
52    /// Acquire this advisory lock (session-scoped, blocks until acquired).
53    pub async fn acquire(&self, conn: &mut Connection) -> Result<PgAdvisoryLockGuard> {
54        conn.execute("SELECT pg_advisory_lock($1)", &[&self.key])
55            .await?;
56        Ok(PgAdvisoryLockGuard { key: self.key })
57    }
58
59    /// Try to acquire this advisory lock without blocking.
60    ///
61    /// Returns `None` if the lock is already held by another session.
62    pub async fn try_acquire(&self, conn: &mut Connection) -> Result<Option<PgAdvisoryLockGuard>> {
63        let rows = conn
64            .query("SELECT pg_try_advisory_lock($1)", &[&self.key])
65            .await?;
66        let acquired: bool = rows
67            .first()
68            .map(|r| r.try_get::<bool>(0))
69            .transpose()?
70            .unwrap_or(false);
71        if acquired {
72            Ok(Some(PgAdvisoryLockGuard { key: self.key }))
73        } else {
74            Ok(None)
75        }
76    }
77}
78
79/// A guard representing a held advisory lock.
80///
81/// The lock is NOT automatically released on drop — you must call `release()`.
82/// This is intentional because releasing requires an async database call.
83#[derive(Debug)]
84pub struct PgAdvisoryLockGuard {
85    key: i64,
86}
87
88impl PgAdvisoryLockGuard {
89    /// The numeric key for this lock.
90    pub fn key(&self) -> i64 {
91        self.key
92    }
93
94    /// Release the advisory lock.
95    pub async fn release(self, conn: &mut Connection) -> Result<()> {
96        conn.execute("SELECT pg_advisory_unlock($1)", &[&self.key])
97            .await?;
98        Ok(())
99    }
100}