Distributed Locks

Keep workers from racing on the same task

Use distributed locks to choose one agent or function for a task, renew its ownership while it works, and let other workers move on.

locks
# three agents reach for the same ticketagent-a  withLock('ticket:4821')  acquired · fencing 17agent-b  withLock('ticket:4821')  acquired: false · takes ticket:4822agent-c  withLock('ticket:4821')  acquired: false · takes ticket:4823agent-a  lease renewed every 10s while it works✓ released · ticket 4821 handling complete
Overview

Stop duplicate workers from running the same job

Two agents reach for the same ticket. A scheduled job starts while the last run is still working. Give that work a named lock: one caller gets ownership, and the others can skip it.

Each lock is a lease with a TTL from 5 seconds to 90 days. The JavaScript withLock helper renews it while your callback runs and releases it when the callback ends. Each new holder also gets a higher fencing token, so your data store can reject a write that carries an older one.

Keep ownership while working

Renew the lock for as long as the job needs it

JavaScript’s withLock acquires the lease, renews at about a third of the TTL, and releases when the callback ends. If renewal fails, it aborts the signal passed to your callback. Check that signal in your callback and stop when it fires.

  • Choose a TTL from 5 seconds to 90 days
  • A held lock returns acquired: false without waiting
  • A crashed holder’s unrenewed lease expires
one holder, start to finish
await volcano.locks.withLock('nightly-rollup', { ttl: 30 }, run)acquired   expires in 30s · fencing 4503599627370497renewed    expires in 30srenewed    expires in 30sreleased · the next run can take it
Protect against stale workers

Reject writes with an older fencing token

A stalled worker can wake up after its lease expires. Pass the fencing token with each write, and have the data store reject tokens below the highest it has recorded. The lock alone cannot stop that worker’s code.

  • The token rises for each new holder of a lock
  • Renewals keep the same token
  • Enforce the check with a conditional UPDATE
two holders, one row
update job_state set cursor = $1, fencing_token = $2  where id = $3 and fencing_token <= $2;holder with token 18 (current)   UPDATE 1holder with token 17 (stalled)   UPDATE 0 · stopsthe stale write was refused by the data itself
Share a backlog

Let each worker claim a different task

Give each task a key, such as ticket:4821. A worker that acquires the lock handles the task, and a worker that loses moves on. If the holder crashes, another caller can acquire the lock after its lease expires and the one-second takeover grace period passes.

  • One current lease per task key
  • Keys are independent across projects
  • Longer TTLs reduce renewal requests
three agents, one backlog
agent-a  ticket:4821  acquiredagent-b  ticket:4821  acquired: false → ticket:4822 acquiredagent-c  ticket:4822  acquired: false → ticket:4823 acquiredagent-a  crashed · lease on ticket:4821 runs out in 60sticket:4821 taken over by agent-b · fencing 18
Capabilities

What keeps workers coordinated

Choose when ownership expires

Set a TTL from 5 seconds to 90 days. A dead holder’s lock frees itself when the TTL runs out.

Renew automatically with withLock

The JavaScript helper acquires, renews, and releases. If a renewal fails, the callback’s signal aborts.

Fence writes from old holders

Store the highest fencing token seen for a lock and reject writes carrying a lower one.

Skip work that is already claimed

JavaScript returns acquired: false for a held lock, so the worker can move on without waiting.

Inspect and recover a lock

Read lock status without owning it. Force-release an abandoned lease only with fencing enforced on the protected resource.

Use your backend language

Acquire, renew, and release through JavaScript, Python, or Ruby SDKs, or call the REST API.

Code

Claim a task and guard its writes

JavaScript
const { VolcanoAuth } = require('@volcano.dev/sdk'); // Locks take a service key, so run this in a function or on your own server. const volcano = new VolcanoAuth({ apiUrl: process.env.VOLCANO_API_URL, anonKey: process.env.VOLCANO_ANON_KEY, accessToken: process.env.VOLCANO_SERVICE_KEY, }); // Every agent runs this over the same backlog. At most one agent holds a // ticket's lease at a time; the others get acquired: false and move on. async function takeNextTicket(tickets) { for (const ticket of tickets) { const result = await volcano.locks.withLock( `ticket:${ticket.id}`, { ttl: 60 }, ({ signal, lease }) => answerTicket(ticket, { signal, fencingToken: lease.fencingToken }), ); if (result.error) throw result.error; if (result.acquired) return result.data; } return null; }
Use cases

What people coordinate with locks

support-agents · backlog of 40 tickets
12 agents running40 tickets claimed · 0 concurrent claims1 agent crashed · its ticket reclaimed after 60s✓ backlog cleared, every ticket answered
Platform

Works with the rest of Volcano

Durable functions
  • Coordinate durable executions

    Acquire a shared resource’s lock inside a durable step before changing it.

Functions and agents
  • Elect one function as leader

    Use withLock for leader election: one function holds the lease and the others skip the work.

Databases and vector
  • Guard database updates

    Store the highest fencing token beside the protected row. Reject lower tokens with a conditional UPDATE.

Realtime
  • Tell waiting agents

    Broadcast when a holder finishes, so waiting agents pick up the next task without polling.

Frequently asked questions

Read the docs
Can I use distributed locks in serverless functions?

Serverless functions and other trusted backend code can use Volcano locks with a project service key. A scoped service key needs locks.manage permission. Lock keys are independent across projects, and browser credentials cannot acquire them.

What happens to a distributed lock when a worker crashes?

A crashed worker’s lease expires when it is no longer renewed. Another caller can acquire the lock one second after expiry, allowing for clock skew. A shorter TTL means a faster takeover, but more renewal requests.

Does a distributed lock wait when another worker holds it?

Volcano lock acquisition does not wait for another holder to finish. JavaScript returns acquired: false, and Python raises ConflictError. Skip the task, try another key, or retry later.

What is a fencing token, and why do I need one?

A fencing token is a number that increases with each new holder of a lock, so your data store can reject writes with older tokens. A worker can keep running after its lease expires. Pass the token with every write and reject values below the highest recorded for that lock. Renewal alone cannot prevent stale writes.

How many distributed locks can a project hold?

About 100 at once with a 30-second TTL, or about 200 with a 60-second TTL. Each project gets 600 lock operations a minute, and every holder renews at a third of its TTL, so renewals use most of that budget. Longer TTLs fit more holders but slow the takeover after a crash.

How is a lock different from a durable execution name?

An execution name deduplicates a start request; a lock coordinates access to a resource across workers or executions. Repeating a start with the same name returns the existing execution while that execution remains readable. Use an execution name for retries of one start and a lock for shared work across separate executions.

Coordinate your workers with locks

Build, deploy, and scale on Volcano's global platform — free to start, with no infrastructure to manage.

Checkout more features