cc.me / Library

Library.

A distributed semaphore over HTTP. Register a pool of count numbered slots under a secret UUID, then borrow one for a bounded time, optionally waiting for a slot to free up — each borrow hands you a slot position in 0 … count − 1. Return a lease early by its UUID, or let it expire on its own. Whoever holds the resource UUID may borrow — the UUID is the only credential.

Use cases

Model

A resource is a pool of count numbered slots, identified by a UUID you choose. The slots are the integers 0 … count − 1. A lease is one held slot, identified by its own server-generated UUID, with an expiry; borrowing hands you the lowest free slot's position. At any instant the pool has in_use active (unexpired, unreturned) leases and available = count − in_use free slots.

Resources

Register a pool

PUT /l/<id> registers the pool at the UUID you choose with a count. It is idempotent: the same call on an existing pool updates its count, so this is also how you grow or shrink the pool. Raising count can immediately satisfy waiting borrowers; lowering it below in_use does not recall live leases — it just blocks new borrows until enough expire or return. count must be between 0 and 1000.

PUT /l/9f1c2e7a-5b3d-4c8e-a1f0-6d2b9c4e7a01

{
  "count": 4
}
{
  "id": "9f1c2e7a-5b3d-4c8e-a1f0-6d2b9c4e7a01",
  "count": 4,
  "in_use": 0,
  "available": 4
}

Inspect

GET /l/<id> returns the current pool state. It does not reveal lease UUIDs — those are separate secrets held by borrowers.

GET /l/9f1c2e7a-5b3d-4c8e-a1f0-6d2b9c4e7a01
{
  "id": "9f1c2e7a-5b3d-4c8e-a1f0-6d2b9c4e7a01",
  "count": 4,
  "in_use": 2,
  "available": 2
}

Delete

DELETE /l/<id> removes the pool and all of its leases. Outstanding lease UUIDs stop being valid. The call is idempotent.

DELETE /l/9f1c2e7a-5b3d-4c8e-a1f0-6d2b9c4e7a01
{
  "deleted": true
}

Borrowing

POST /l/<id>/borrow reserves one permit. ttl (seconds) is how long the lease is held before it auto-expires. wait (seconds, default 0) is how long the call may block when the pool is exhausted. Both are clamped to their configured maximums.

POST /l/9f1c2e7a-5b3d-4c8e-a1f0-6d2b9c4e7a01/borrow

{
  "ttl": 30,
  "wait": 25
}

On success the response carries the lease UUID, the slot position you were given, and the expiry:

{
  "lease": "3a7d0b14-9e2c-4f6a-8b1d-0c5e2f9a7b34",
  "position": 0,
  "expires_at_unix": 1781337630,
  "expires_in": 30
}

Returning

POST /l/<id>/return frees a lease before its ttl elapses and wakes any borrower waiting on the pool. It is idempotent: returning an already-expired, already-returned, or unknown lease reports returned: false.

POST /l/9f1c2e7a-5b3d-4c8e-a1f0-6d2b9c4e7a01/return

{
  "lease": "3a7d0b14-9e2c-4f6a-8b1d-0c5e2f9a7b34"
}
{
  "returned": true
}

You never have to return: every lease is freed automatically ttl seconds after it was borrowed. Returning early just makes the permit available sooner. Pick a ttl long enough to finish the work but short enough that a crashed holder doesn't strand the permit.

Errors

Errors use the HTTP status plus a compact JSON body. Treat 5xx as retryable with backoff; treat 4xx as a caller problem to fix first.

{
  "error": "human-readable reason"
}
400
Malformed JSON, an ID that is not a UUID, or count, ttl, or wait outside the allowed range.
404
No pool exists for that resource UUID (it was never registered or was deleted).
409
Borrow could not acquire a permit within wait seconds. Retry later or with a longer wait.
502
The database command failed. Safe to retry.