Distributed Locks
Each {namespace}/{key} has one lock slot: one owner at a time, a TTL
that auto-releases if the holder crashes, and a fencing token for
downstream safety. Every behavior on this page was reproduced against a
live server.
Acquire
snug --output json distributed-state acquire-lock -n docs4-jobs -k nightly-report -o worker-a -t 60{
"acquired_at": "2026-08-28T04:33:32.065175Z",
"expires_at": "2026-08-28T04:34:32.065175Z",
"fence_token": 1,
"key": "nightly-report",
"lock_id": "lock_XXvDUxusTSXY",
"metadata": null,
"namespace": "docs4-jobs",
"owner_id": "worker-a",
"ttl_seconds": 60
}owner_id is an identity you choose (instance ID, hostname) - it is not
the authenticated user. Keep the returned lock_id: extend and release
require both. TTL defaults to 30 seconds when omitted. Optional
-m '{"job":"nightly"}' attaches JSON metadata, echoed back on status.
Contention: fail fast or wait
By default (-w 0) a held lock rejects the acquire immediately. The 409
envelope is unusual - it carries data alongside the error code, telling
you who holds it and when to come back:
{
"status": 409,
"msg": "Lock already held by 'worker-a'",
"error": "lock_already_held",
"data": {
"current_owner": "worker-a",
"expires_at": "2026-08-28T04:34:32.065175Z",
"retry_after_ms": 59980
}
}With -w <ms> the acquire blocks until the lock frees or the wait runs
out (408 lock_timeout). Verified end to end: worker-a held a 2-second
lock, worker-c called with -w 5000, blocked 2.0 seconds, and acquired
the moment the TTL expired:
snug distributed-state acquire-lock -n docs4-jobs -k nightly-report -o worker-a -t 2
snug distributed-state acquire-lock -n docs4-jobs -k nightly-report -o worker-c -t 30 -w 5000That is also the crash story: a holder that dies without releasing blocks nobody for longer than its TTL.
Holding: status and renewal
snug --output json distributed-state lock-status -n docs4-jobs -k nightly-report
snug distributed-state extend-lock -n docs4-jobs -k nightly-report \
-l lock_XXvDUxusTSXY -o worker-a -t 120Status shows is_locked plus holder details and time_remaining_ms (all
null when unlocked). For work that outlives the TTL, keep the TTL short
and renew with extend-lock before expiry rather than acquiring with a
huge TTL - a crash then frees the lock quickly. Extending resets
expires_at and leaves fence_token unchanged.
Release, and the ownership guards
snug distributed-state release-lock -n docs4-jobs -k nightly-report \
-l lock_XXvDUxusTSXY -o worker-aExtend and release require the matching pair, and each mismatch fails distinctly (all reproduced live):
- wrong
owner_id-403 lock_not_owned - wrong
lock_id-409 lock_id_mismatch - no lock held (already released or expired) -
404 lock_not_found
These guards are cooperative, not access control: owner_id and lock_id
are strings any caller in the project could present. They prevent
accidents - a stale worker renewing a lock it lost - not malice.
Fence tokens
fence_token is a monotonic counter per lock key, incremented on every
successful acquire - including a re-acquire after the previous holder's
TTL expired. Verified live: four successive acquires on one key returned
tokens 1, 2, 3, 4, with the expiry takeover included.
Use it when the locked work touches an external system: pass the token along with each write, and have the downstream reject tokens lower than the highest it has seen. A holder that lost its lock to expiry (and thinks it still holds it) then carries a stale token and is rejected, instead of corrupting the successor's work. Extending preserves the token, so renewal does not invalidate in-flight writes.
Reference
Endpoints and exact request fields: Distributed Locking API.