use sqlx::pool::PoolConnection;
use sqlx::{PgPool, Postgres};
pub struct AdvisoryLock {
key: i64,
conn: PoolConnection<Postgres>,
}
impl AdvisoryLock {
pub async fn release(mut self) {
match sqlx::query_scalar::<_, bool>("SELECT pg_advisory_unlock($1)")
.bind(self.key)
.fetch_one(&mut *self.conn)
.await
{
Ok(true) => {
tracing::debug!(key = self.key, "released advisory lock");
}
Ok(false) => {
tracing::warn!(
key = self.key,
"advisory unlock returned false (lock was not held)"
);
}
Err(err) => {
tracing::warn!(key = self.key, %err, "failed to release advisory lock");
}
}
}
}
pub async fn try_acquire(
pool: &PgPool,
database_identity: &str,
) -> Result<Option<AdvisoryLock>, sqlx::Error> {
let key = advisory_lock_key(database_identity);
let mut conn = pool.acquire().await?;
let acquired: bool = sqlx::query_scalar("SELECT pg_try_advisory_lock($1)")
.bind(key)
.fetch_one(&mut *conn)
.await?;
if acquired {
tracing::info!(key, database_identity, "acquired advisory lock");
Ok(Some(AdvisoryLock { key, conn }))
} else {
tracing::info!(
key,
database_identity,
"advisory lock contention — another session holds the lock"
);
Ok(None)
}
}
fn advisory_lock_key(identity: &str) -> i64 {
const OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
const PRIME: u64 = 0x0100_0000_01b3;
let mut hash = OFFSET_BASIS;
for byte in b"pgroles:".iter().chain(identity.as_bytes()) {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(PRIME);
}
(hash >> 1) as i64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn advisory_lock_key_deterministic() {
let a = advisory_lock_key("prod/db-creds/DATABASE_URL");
let b = advisory_lock_key("prod/db-creds/DATABASE_URL");
assert_eq!(a, b, "same identity must produce same key");
}
#[test]
fn advisory_lock_key_different_for_different_identities() {
let a = advisory_lock_key("prod/db-creds/DATABASE_URL");
let b = advisory_lock_key("staging/db-creds/DATABASE_URL");
assert_ne!(a, b, "different identities must produce different keys");
}
#[test]
fn advisory_lock_key_different_secret_keys() {
let a = advisory_lock_key("prod/db-creds/DATABASE_URL");
let b = advisory_lock_key("prod/db-creds/CUSTOM_URL");
assert_ne!(a, b, "different secret keys must produce different keys");
}
#[test]
fn advisory_lock_key_is_positive() {
let key = advisory_lock_key("prod/db-creds/DATABASE_URL");
assert!(key >= 0, "advisory lock key should be non-negative");
}
#[test]
fn advisory_lock_key_namespace_prefix_avoids_collision() {
let a = advisory_lock_key("x");
let b = advisory_lock_key("y");
assert_ne!(a, b);
}
#[test]
fn advisory_lock_key_empty_identity() {
let key = advisory_lock_key("");
assert!(key >= 0);
}
}