ServicesLease

Lease

Exclusive, time-bound access to unique resources: one holder owns the world instance, the compute slot, or the license seat until the lease expires or is released. Resources carry a policy (durations, transferability, shared modes, fractional units), every grant carries a monotonic fencing token, contention can queue in a waiting room, and every lifecycle event lands in a durable history. Every example on this page was executed against a live server.

When to reach for it: one game server per match, license seats, a publish lock on a world instance, machine ownership during maintenance windows, transferable ownership between users and agents, and auditing who held what, when, and how it ended.

When not to: low-level coordination primitives - a mutex or compare-and-swap around a critical section - belong to Distributed State locks; a lease is a product-level grant with policy, queueing, and history. Queueing people into a capacity-limited experience at scale is the Waiting Room service - the lease waiting room queues callers for one resource.

Concepts

  • Resources are registered up front (platform admin token required) with a lease policy: default/max duration, renewal grace, transferability, shared modes, fractional units, lifetime caps. The resource_id is server-generated.
  • A lease is one holder's time-bound claim. acquire returns a lease_id, expires_at, and a fencing token; the lease expires on its own unless renewed or released.
  • Fencing tokens are a per-resource monotonic counter, bumped on every acquire and transfer. Renew, release, and transfer all demand the current token, so a superseded holder cannot mutate the lease.
  • Modes - exclusive (the default) locks the whole resource; read and write leases can coexist with each other when the policy lists them in shared_modes.
  • Waiting room - acquiring with on_conflict = join_waiting_room queues the caller instead of failing; see Contention: waiting room and bookings.
  • History durably records every acquire, renew, transfer, release, and reclamation with actor, fencing token, and status transition.

Register a resource

Registration is admin-only (a non-admin gets 403 insufficient_permissions); acquiring is not. There is no delete or update endpoint for resources - register them deliberately.

snug lease resources create --kind world_instance --display-name "World A" \
  --transferable --shared-mode read --max-duration-seconds 7200
snug lease resources get baubzZduHFaJUnXbcPSh   # policy, active leases, bookings, wait count

Acquire, renew, release

snug lease acquire baubzZduHFaJUnXbcPSh --duration 30m --metadata purpose=world-publish
snug lease status baubzZduHFaJUnXbcPSh
snug lease renew baubzZduHFaJUnXbcPSh --lease lse_PcyrehtbgqTE --fencing-token 1 --duration 30m
snug lease release baubzZduHFaJUnXbcPSh --lease lse_PcyrehtbgqTE --fencing-token 1

Over HTTP, acquisition is a POST:

curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"duration_seconds": 1800, "metadata": {"purpose": "world publish"}}' \
  http://localhost:4000/api/v1/leases/baubzZduHFaJUnXbcPSh/acquire
{
  "status": 201,
  "msg": "Created",
  "data": {
    "lease_id": "lse_JbhuLZhxLHyd",
    "resource_id": "baubzZduHFaJUnXbcPSh",
    "status": "active",
    "mode": "exclusive",
    "fencing_token": 8,
    "expires_at": "2026-08-28T07:04:22.444553Z"
  }
}

While someone else holds the resource, acquire fails with 409 resource_unavailable. Retrying your own acquire with the same --idempotency-key returns the original lease instead of erroring.

Behaviors to know, each reproduced live:

  • Renew replaces the expiry with now + duration - it does not append to the current expiry. The total (time already elapsed + requested duration) is checked against the resource's max duration.
  • A stale fencing token is rejected: renewing with the wrong token fails 409 stale_fencing_token with expected N, got M in the message.
  • Only the holder (or an admin) may renew, release, or transfer - anyone else gets 403 insufficient_permissions, even with a valid lease id and token.

Expiry, grace, and reclamation

An expired lease does not free the resource at expires_at. The holder keeps exclusivity through the renewal grace window (60 seconds by default), during which it can still renew; other callers keep getting 409 resource_unavailable. After the grace passes, the background reclamation worker (every 30 seconds by default) sweeps the lease, appends a reclaimed event to history, and admits the next waiter. Verified live: a 5-second lease still showed active 11 seconds after expiry, and history recorded reclaimed 71 seconds after. Plan for the resource staying occupied up to grace + one worker interval past expiry; release explicitly for an instant handoff.

Shared modes and fractional units

With shared_modes: ["read"], two holders' --mode read leases coexist, each with its own lease id and fencing token; an exclusive acquire during that time fails 409 resource_unavailable, and a mode not in the policy fails 400 invalid_mode. A fractional policy (fractional: {enabled: true, total_units: 4}, HTTP-only at registration) makes shared-mode leases draw --units from a common pool: after one holder takes 3 of 4 units, a 2-unit request fails 409 insufficient_units while a 1-unit request succeeds. Units do not pool in exclusive mode - an exclusive lease always locks the whole resource regardless of units.

Transfer

On a transferable resource, the holder hands the lease over without a release/acquire gap:

snug lease transfer baubzZduHFaJUnXbcPSh --lease lse_eXeaApMyvWNM \
  --fencing-token 2 --to-holder docs-wave

Expiry is preserved; the fencing token rotates, so the old holder's token is dead the moment the transfer lands.

Validation and FSM guards

validate lets a downstream service check a presented lease id + fencing token before doing work on the holder's behalf. It returns 200 with a valid boolean and a reason - branch on valid, not the status code: a stale token yields valid: false with stale fencing token (expected 3, got 2). Resources may also declare fsm_requirements (machine + transition pairs); validating with --machine/--transition yields no matching fsm requirement unless the resource declares that pair.

History

snug lease history baubzZduHFaJUnXbcPSh --page-size 20 --sort-order asc

Every entry carries the event (acquired, renewed, transferred, released, reclaimed, ...), actor, fencing token at the time, status transition, and metadata, with standard pagination; newest-first by default.

Limits and configuration

Default lease duration 1 hour, max 24 hours, renewal grace 60 seconds, reclamation every 30 seconds, up to 10000 waiters and 16 KB of lease metadata by default - all tunable via the LEASE_* variables in the Lease CONFIG reference.

Reference

On this page