pub struct DirLock { /* private fields */ }Expand description
Advisory, single-writer lock on a database directory (ADR-hjxp/0006), claimed with
O_CREAT|O_EXCL and kept alive by a heartbeat that is written to a fresh temporary and renamed
over the lock file (ADR-6wjp/0016), so a stale timestamp is never mistaken for proof that the
holder is gone and no write ever follows a link somebody planted in the way.
The lease — stale_after_ms since the last heartbeat — is the whole liveness story, and holds
on every platform. Where processes can be asked about (unix), a holder whose pid is gone is
reclaimed sooner; that is an accelerator and nothing rests on it.
The heartbeat runs on a background thread where threads exist. Where they do not, or where one
could not be started, DirLock::tick and the renewal every write already performs are what
keeps the lease alive — call tick from a host timer at least twice per heartbeat_ms.
Implementations§
Source§impl DirLock
impl DirLock
Sourcepub fn acquire(
path: impl Into<PathBuf>,
options: LockOptions,
) -> Result<DirLock, DirLockError>
pub fn acquire( path: impl Into<PathBuf>, options: LockOptions, ) -> Result<DirLock, DirLockError>
path is the absolute path of the lock file itself, not of the directory holding it.
Sourcepub fn acquire_as(
path: PathBuf,
options: LockOptions,
subject: PathBuf,
) -> Result<DirLock, DirLockError>
pub fn acquire_as( path: PathBuf, options: LockOptions, subject: PathBuf, ) -> Result<DirLock, DirLockError>
As Self::acquire, but names subject — the database root, not the lock file — in a
resulting LockedError.
Sourcepub fn challenge(
path: impl Into<PathBuf>,
options: LockOptions,
) -> Result<ChallengeOutcome, DirLockError>
pub fn challenge( path: impl Into<PathBuf>, options: LockOptions, ) -> Result<ChallengeOutcome, DirLockError>
One pass of the acquire protocol that waits for nothing, so that every wait belongs to the
caller. Where the engine runs on the one thread a host has — wasm32-wasip1 — this and
Self::finish_challenge are what let a reclaim happen without the module sleeping
inside a call the host is waiting on.
timeout_ms and retry_interval_ms are not read: there is nothing here to time out. A
lock file that vanishes underneath the pass is retried at once, up to the same small bound
Self::acquire uses, because that costs no waiting either.
@param path the absolute path of the lock file itself, not of the directory holding it
@param options the lease tuning — stale_after_ms and heartbeat_ms as acquire reads them
@returns the lock, a refusal to reclaim, or the grace a hand-over is owed first
Sourcepub fn finish_challenge(
path: impl Into<PathBuf>,
options: LockOptions,
holder: &LockHolder,
) -> Result<Option<DirLock>, DirLockError>
pub fn finish_challenge( path: impl Into<PathBuf>, options: LockOptions, holder: &LockHolder, ) -> Result<Option<DirLock>, DirLockError>
The far side of a ChallengeOutcome::GraceOwed, for a caller that has waited grace_ms
out on its own clock. Reclaims the lock only if the lease is still the one that was
challenged, and does no waiting of its own.
Two callers finishing the same challenge at the same moment are safe the way two
Self::acquire calls are: the lock file ends up naming exactly one of them, and any
other that believed it won is fenced the first time it calls Self::tick.
@param path the absolute path of the lock file itself, not of the directory holding it
@param options the lease tuning — only heartbeat_ms is read
@param holder the holder the challenge named, unchanged
@returns the lock, or None when somebody proved alive there or reclaimed it first — in
which case the caller is back to Self::challenge
Sourcepub fn inspect(
path: impl AsRef<Path>,
options: LockOptions,
) -> Result<LockState, DirLockError>
pub fn inspect( path: impl AsRef<Path>, options: LockOptions, ) -> Result<LockState, DirLockError>
Reads what the lock file says without claiming, reclaiming or removing anything — the only entry point here that leaves the directory exactly as it found it, and so the only one an observer can use against a database somebody else is writing.
The answer is a snapshot of one moment: a holder reported as LockState::Held can release
the lock the instant after, and a lease reported as expired can be renewed. It is a
diagnosis, never a reservation — nothing here entitles the caller to the lock.
@param path the absolute path of the lock file itself, not of the directory holding it
@param options the lease tuning — only stale_after_ms is read, to date the lease
@returns what the lock file holds, or DirLockError::SuspiciousLockFile when something
other than a regular file sits at path
Sourcepub fn tick(&self) -> Result<(), DirLockError>
pub fn tick(&self) -> Result<(), DirLockError>
Renews the heartbeat if heartbeat_ms has elapsed since the last renewal, and does nothing
otherwise — cheap enough to call on every write, and safe to call from a host timer. This
is the only renewal a platform without background threads gets, so a host that drives one
must call it at least twice per heartbeat_ms.
Returns DirLockError::Lost once the lock has been reclaimed by somebody else; a handle
that has heard that must stop writing, and every later call repeats it.
Sourcepub fn is_lost(&self) -> bool
pub fn is_lost(&self) -> bool
Whether this handle still holds the lock as far as it knows — false once a tick or a
heartbeat has caught somebody else’s token in the lock file.