SOLENOID.SYSTEMS [...]
SYS ENG PHI PRICING
API
├─ CATCH ├─ GATE ├─ KEY ├─ LATCH ├─ METER ├─ PULSE ├─ RELAY └─ WITNESS

Add Distributed Locks to Your API in 5 Minutes

#distributed-systems #locking #latch #tutorial

Two API servers receive the same webhook simultaneously. Both process the payment. Customer is charged twice.

Or two deploy scripts run concurrently. Both try to migrate the database. One wins. One corrupts the migration state.

You need distributed locking. Ensure only one process does the work.

The 5-Minute Version

Latch provides distributed locks with FIFO fairness and automatic TTL-based release.

Step 1: Acquire a lock

curl -X POST "https://api.solenoid.systems/v1/latch/deploy:prod/acquire" \
  -H "Authorization: Bearer sm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"ttl": 10000, "timeout": 10000}'

Response:

{
  "status": "acquired",
  "token": "550e8400-e29b-41d4-a716-446655440000",
  "expires_at": 1706123466,
  "key": "deploy:prod"
}

You have the lock. The token proves it.

Step 2: Do your work

While you hold the lock, no other client can acquire it. They wait in a FIFO queue.

Step 3: Release the lock

curl -X POST "https://api.solenoid.systems/v1/latch/deploy:prod/release" \
  -H "Authorization: Bearer sm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"token": "550e8400-e29b-41d4-a716-446655440000"}'
{ "status": "released" }

Next waiter in the queue gets the lock automatically.

Handling the Edge Cases

Auto-release on crash: Your process crashes without releasing the lock. The TTL expires. Latch releases automatically. Next waiter gets it.

No manual intervention. No leaked locks blocking forever.

Reentrant locking: Already hold the lock and need to extend it?

let token = null

// First acquire
const res1 = await fetch(url + '/acquire', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ ttl: 10000, timeout: 10000 })
})
token = (await res1.json()).token

// Work takes longer than expected - refresh TTL
const res2 = await fetch(url + '/acquire', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ ttl: 10000, token })
})

const result = await res2.json()
// result.reentrant === true
// TTL refreshed, same token returned

FIFO fairness: Locks are granted in request order. No starvation.

T=0s:  Client A acquires lock
T=1s:  Client B requests (waits)
T=2s:  Client C requests (waits)
T=5s:  Client A releases → B gets lock immediately
T=8s:  Client B releases → C gets lock immediately

Client C never jumps ahead of Client B.

Real-World Patterns

Prevent duplicate webhook processing:

app.post('/webhooks/stripe', async (req, res) => {
  const eventId = req.body.id

  // Try to acquire lock for this event
  const lockRes = await fetch(
    `https://api.solenoid.systems/v1/latch/webhook:${eventId}/acquire`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.SOLENOID_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ ttl: 30000, timeout: 1000 })
    }
  )

  if (lockRes.status === 408) {
    // Another server is already processing this webhook
    return res.json({ received: true })
  }

  const { token } = await lockRes.json()

  try {
    // Process webhook
    await processStripeEvent(req.body)

    // Release lock
    await fetch(
      `https://api.solenoid.systems/v1/latch/webhook:${eventId}/release`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.SOLENOID_API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ token })
      }
    )

    return res.json({ received: true })
  } catch (error) {
    // Lock auto-releases after TTL even if we crash here
    throw error
  }
})

If Stripe retries a webhook to multiple servers, only one processes it.

Deployment coordination:

#!/bin/bash
# deploy.sh

# Acquire lock (blocks until available)
LOCK_RESPONSE=$(curl -X POST "https://api.solenoid.systems/v1/latch/deploy:production/acquire" \
  -H "Authorization: Bearer $SOLENOID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttl": 300000, "timeout": 300000}')

TOKEN=$(echo $LOCK_RESPONSE | jq -r '.token')

if [ -z "$TOKEN" ]; then
  echo "Failed to acquire deployment lock"
  exit 1
fi

# Run deployment (exclusive access guaranteed)
echo "Running database migrations..."
npm run migrate

echo "Deploying services..."
npm run deploy

# Release lock
curl -X POST "https://api.solenoid.systems/v1/latch/deploy:production/release" \
  -H "Authorization: Bearer $SOLENOID_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"token\": \"$TOKEN\"}"

echo "Deployment complete"

Multiple engineers can run this simultaneously. The second one waits for the first to finish.

Scheduled job coordination:

// Cron runs on multiple servers - only one should execute
async function runDailyReport() {
  let token = null

  try {
    const res = await fetch(
      'https://api.solenoid.systems/v1/latch/cron:daily-report/acquire',
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.SOLENOID_API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ ttl: 60000, timeout: 1000 })
      }
    )

    if (res.status === 408) {
      // Another server is running the job
      console.log('Job already running on another server')
      return
    }

    const data = await res.json()
    token = data.token

    // Run the job
    await generateDailyReport()
    await sendReportEmail()

  } finally {
    if (token) {
      await fetch(
        'https://api.solenoid.systems/v1/latch/cron:daily-report/release',
        {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.SOLENOID_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ token })
        }
      )
    }
  }
}

Why Not Build This Yourself?

You could use Redis with SET NX EX. Set a key if it doesn’t exist, with expiration.

But that’s advisory locking. Race conditions exist between checking and setting. You need Lua scripts for atomicity. You need to handle Redis connection failures. You need to monitor for leaked locks.

Or you use Postgres SELECT FOR UPDATE. But now every lock check hits your primary database. Lock contention becomes database load.

Latch uses Cloudflare Durable Objects. Strongly consistent single-threaded execution per key. Locks are truly exclusive. Fairness is guaranteed by request order. Auto-release is built in.

You acquire locks. Cloudflare ensures mutual exclusion.

Try It Now

Free tier: 10,000 calls to prototype. Pro tier ($49.99/mo) and above: unlimited Latch usage.

# Acquire a lock
curl -X POST "https://api.solenoid.systems/v1/latch/test-lock/acquire" \
  -H "Authorization: Bearer sm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"ttl": 10000, "timeout": 10000}'

# Check status (doesn't cost credits)
curl "https://api.solenoid.systems/v1/latch/test-lock" \
  -H "Authorization: Bearer sm_your_api_key"

# Release with the token from acquire response
curl -X POST "https://api.solenoid.systems/v1/latch/test-lock/release" \
  -H "Authorization: Bearer sm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"token": "YOUR_TOKEN_HERE"}'

Open two terminal windows. Acquire in both simultaneously. The second one waits for the first to release.

Your distributed locking, in three API calls.