/ Docs

Distributed locks

Coordinate Ruby 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.

require "volcano"

client = Volcano::Client.new(
  anon_key: ENV.fetch("VOLCANO_ANON_KEY"),
  service_key: ENV.fetch("VOLCANO_SERVICE_KEY"),
  api_url: ENV.fetch("VOLCANO_API_URL", "https://api.volcano.dev")
)
lease = client.locks.acquire("daily-report", ttl: 30)
begin
  puts "Lease acquired #{lease.fencing_token}"
ensure
  client.locks.release("daily-report", lease)
end

acquire returns an immutable Volcano::LockLease containing the key, ownership token, expiration, and fencing token. Release and renew using that lease; the ownership token identifies the holder. A competing owner can cause Volcano::Error::ConflictError.

Renew or inspect a lease

state = client.locks.get("daily-report")
puts [state.held, state.expires_at, state.fencing_token]

lease = client.locks.acquire("daily-report", ttl: 30)
begin
  lease = client.locks.renew("daily-report", lease, ttl: 60)
ensure
  client.locks.release("daily-report", lease)
end

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

client.locks.with_lock("daily-report", ttl: 30) do |guard|
  puts "Current fence #{guard.lease.fencing_token}"
  raise "Lock ownership was lost" if guard.lost?
end

The block helper 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.

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:

require "securerandom"

owner_token = SecureRandom.uuid
request_id = SecureRandom.uuid
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.

On this page