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}