Distributed locks
Coordinate Python workers with project-scoped lock leases, renewal, and fencing tokens.
Acquire a project-scoped lease from trusted server code using a service key.
Set VOLCANO_ANON_KEY and VOLCANO_SERVICE_KEY for the same project.
import os
from volcano_sdk import VolcanoClient
client = VolcanoClient(
anon_key=os.environ["VOLCANO_ANON_KEY"],
service_key=os.environ["VOLCANO_SERVICE_KEY"],
api_url=os.environ.get("VOLCANO_API_URL", "https://api.volcano.dev"),
)
lease = client.locks.acquire("daily-report", ttl=30)
try:
print("Lease acquired", lease.fencing_token)
finally:
client.locks.release("daily-report", lease)acquire() returns an immutable LockLease containing the key, ownership token, expiration, and fencing token.
Release and renew using that lease; the ownership token identifies the holder.
TTLs are integer seconds from 5 seconds through 90 days.
A competing owner can cause ConflictError.
Renew or inspect a lease
state = client.locks.get("daily-report")
print(state.held, state.expires_at, state.fencing_token)
lease = client.locks.acquire("daily-report", ttl=30)
try:
lease = client.locks.renew("daily-report", lease, ttl=60)
finally:
client.locks.release("daily-report", lease)get() reports availability without acquiring the lock.
renew() returns a new immutable lease, so retain its return value.
Renew for the duration of a block
with client.locks.with_lock("daily-report", ttl=30) as guard:
print("Current fence", guard.lease.fencing_token)
if guard.lost:
raise RuntimeError("Lock ownership was lost")The context manager renews in the background and attempts to release its latest lease on exit.
guard.lease reads the latest lease; guard.lost checks ownership loss, and guard.wait_lost(timeout=1.0) waits up to one second for it.
Stop protected work when ownership is lost.
Use fencing tokens in the protected data store so an expired worker cannot overwrite work performed by a newer holder.
Lease renewal alone cannot stop application code that is already running.
If the block succeeds, a renewal failure is raised after cleanup. An exception from the block takes precedence.
Recover an abandoned lock
client.locks.force_release("daily-report") removes the current lease without its ownership token.
Use this only for administrative recovery with fencing enforced by the protected resource.
Recover an uncertain acquisition
Acquisition retries a transport failure or HTTP 503 once with the same ownership token, request ID, key, TTL, and credential. Other HTTP errors are not retried. For recovery after that retry also fails, generate and retain identifiers before acquiring, then reuse them for the same acquisition attempt:
from uuid import uuid4
owner_token = str(uuid4())
request_id = str(uuid4())
lease = client.locks.acquire(
"daily-report", ttl=30, token=owner_token, request_id=request_id
)Caller-supplied UUID strings retain their exact spelling across retries and lease operations.
Every lock method accepts request_id. with_lock() also accepts token and
request_id for acquisition; its renewal and release calls use new request IDs.
Use a new ownership token for a new lease after release or expiry. Keep ownership tokens private. A failed response does not prove the server failed to acquire; reuse the original token to recover the outcome rather than starting a new owner.