distkit
Locks

Guard state & errors

Reading a guard's lease state and handling acquisition failures.

A lock guard tracks the state of its lease. Because the lease renews in the background, it's possible (rarely) for a guard to lose its lease mid-section - for instance, if Redis was briefly unreachable past the ttl. LockGuardState lets you observe that.

LockGuardState

pub enum LockGuardState {
    Acquired, // held, lease is being renewed
    Lost,     // a refresh failed; the lease is presumed gone (may still recover)
    Released, // released, no longer held
}

Every guard - MutexGuard, RwLockReadGuard, RwLockWriteGuard - exposes:

// Inspect the current state. No Redis round-trip.
fn get_state(&self) -> LockGuardState;

// The zero-based attempt that obtained the lock (0 = first poll).
fn get_on_attempt(&self) -> usize;

// Release explicitly and return the resulting state.
async fn release(self) -> Result<LockGuardState, DistkitError>;

How many attempts it took

get_on_attempt reports the zero-based index of the acquire poll that obtained the lock. A lock taken on the very first poll returns 0; each retry while waiting bumps the count, so an acquire that succeeded after three failed polls returns 3. A one-shot try_* makes a single attempt and is always 0.

It's a cheap stored value (no Redis round-trip) — handy for contention metrics or backoff tuning. The poll spacing that separates attempts is retry_interval (see Options).

let guard = mutex.lock().await?;

if guard.get_on_attempt() > 0 {
    // we waited through contention before getting in
}

Checking before a critical section

Lost is the state to guard against: it means the lock may no longer be exclusively yours.

use distkit::lock::LockGuardState;

let guard = mutex.lock().await?;

match guard.get_state() {
    LockGuardState::Acquired => {
        // safe - do the protected work
    }
    LockGuardState::Lost => {
        // lease slipped; bail out instead of risking a double-run
        return Ok(());
    }
    LockGuardState::Released => unreachable!("we still hold the guard"),
}

Acquisition errors

Acquiring can fail with a LockError (surfaced as DistkitError::LockError). The two you handle routinely mean "the lock is busy", not "something broke":

  • AcquireFail - a non-blocking try_* call would have had to wait.
  • Timeout { waited } - a bounded *_for call ran out of time.
use distkit::{DistkitError, lock::LockError};

match mutex.try_lock().await {
    Ok(guard) => { /* got it */ }
    Err(DistkitError::LockError(LockError::AcquireFail)) => {
        // someone else holds it - back off and retry later
    }
    Err(e) => return Err(e.into()),
}

The remaining variants (NotOwner, InvalidTtl, InvalidOwner) indicate misuse or bad configuration rather than contention.