use std::sync::Arc;
use std::time::Duration;
use tokio::time::{Instant as TokioInstant, sleep};
use tracing::{Instrument, info_span};
use crate::backend::LockBackend;
use crate::error::{Error, Result};
use crate::guard::LockGuard;
use crate::watchdog::{self, LeaseState};
use crate::{ClientInner, telemetry};
pub struct LockBuilder<'a> {
pub(crate) inner: &'a ClientInner,
pub(crate) resource: String,
pub(crate) ttl: Duration,
pub(crate) fair: bool,
pub(crate) wait_budget: Duration,
}
impl<'a> LockBuilder<'a> {
pub fn with_ttl(mut self, ttl: Duration) -> Self {
self.ttl = ttl;
self
}
pub fn with_fairness(mut self, fair: bool) -> Self {
self.fair = fair;
self
}
pub async fn acquire(self) -> Result<LockGuard> {
let backend = Arc::clone(&self.inner.backend);
let node_id = self.inner.node_id;
let span = info_span!(
"ash_lock.acquire",
resource_name = %self.resource,
node_id = node_id,
fair = self.fair,
);
self.acquire_inner(backend, node_id).instrument(span).await
}
async fn acquire_inner(self, backend: Arc<dyn LockBackend>, node_id: u64) -> Result<LockGuard> {
if self.ttl.is_zero() {
return Err(Error::Config("lock ttl must be non-zero".into()));
}
let deadline = TokioInstant::now() + self.wait_budget;
let mut attempt: u32 = 0;
loop {
match backend.try_acquire(&self.resource, self.ttl).await {
Ok((token, _lease_deadline)) => {
telemetry::inc_acquisitions();
let stamp = self.inner.clock.stamp()?;
let state = Arc::new(LeaseState::new(
self.resource.clone(),
token,
node_id,
self.ttl,
));
let handle = watchdog::spawn(
Arc::clone(&backend),
Arc::clone(&state),
self.inner.watchdog_interval,
);
return Ok(LockGuard::new(backend, state, handle, stamp));
}
Err(Error::Contended { .. }) => {
if attempt == 0 {
telemetry::inc_contention();
}
attempt += 1;
if TokioInstant::now() >= deadline {
return Err(Error::Timeout {
resource: self.resource.clone(),
waited: self.wait_budget,
});
}
sleep(self.retry_delay(attempt)).await;
}
Err(e) => return Err(e),
}
}
}
fn retry_delay(&self, attempt: u32) -> Duration {
let base = self.inner.watchdog_interval.min(Duration::from_millis(50));
if self.fair {
return base;
}
let backoff = base.saturating_mul(1u32 << attempt.min(5));
let jitter_ns = self
.inner
.node_id
.wrapping_mul(2654435761)
.wrapping_add(attempt as u64)
% (base.as_nanos() as u64).max(1);
backoff.saturating_add(Duration::from_nanos(jitter_ns))
}
}