Distributed scheduler lock built on Redis SET NX PX: instances call dibs on a job; the first one wins, everyone else skips.
When the same scheduled job runs on N instances of a service, exactly one instance should execute it per tick. This namespace provides that guarantee as a higher-order function: the caller hands us a closure and we run it if — and only if — this process wins the distributed lock. This deliberately avoids the annotation + AOP approach because annotations cannot express runtime-computed lock keys, cannot return the job's result to the caller, and silently fail to apply on self-invocation since Spring proxies only intercept external calls.
The namespace is AOT-compiled (:gen-class) into dibs.Dibs
so that Kotlin/Java callers see a plain static-method API with zero Clojure
in the call site:
execute(conn, lockName, atMostSec, atLeastSec, Runnable) -> boolean
executeWithResult(conn, lockName, atMostSec, atLeastSec, Supplier) -> Optional<T>
execute returns whether the lock was won (and therefore whether the task
ran). executeWithResult returns the task's value wrapped in Optional, or
Optional.empty() when this instance was skipped. Note the ambiguity this
implies: a Supplier that legitimately returns null is indistinguishable
from a skip. Callers that care must use execute or return a sentinel.
A lock is a single Redis key whose value is a UUID token unique to the holder. Three rules make it safe:
SET key token NX PX ttl — one atomic command, so two
instances can never both believe they won.conn is either a Redis URI string ("redis://host:6379"), a Carmine
conn map, or a java.util.Map treated as the Carmine :spec map (the shape a
Kotlin caller naturally produces: mapOf("uri" to "redis://...")).
Distributed scheduler lock built on Redis SET NX PX: instances call dibs
on a job; the first one wins, everyone else skips.
## Purpose
When the same scheduled job runs on N instances of a service, exactly one
instance should execute it per tick. This namespace provides that guarantee
as a higher-order function: the caller hands us a closure and we run it
if — and only if — this process wins the distributed lock. This deliberately
avoids the annotation + AOP approach because annotations cannot express
runtime-computed lock keys, cannot return the job's result to the caller,
and silently fail to apply on self-invocation since Spring proxies
only intercept external calls.
## Java surface
The namespace is AOT-compiled (`:gen-class`) into `dibs.Dibs`
so that Kotlin/Java callers see a plain static-method API with zero Clojure
in the call site:
execute(conn, lockName, atMostSec, atLeastSec, Runnable) -> boolean
executeWithResult(conn, lockName, atMostSec, atLeastSec, Supplier) -> Optional<T>
`execute` returns whether the lock was won (and therefore whether the task
ran). `executeWithResult` returns the task's value wrapped in Optional, or
Optional.empty() when this instance was skipped. Note the ambiguity this
implies: a Supplier that legitimately returns null is indistinguishable
from a skip. Callers that care must use `execute` or return a sentinel.
## Locking model
A lock is a single Redis key whose value is a UUID token unique to the
holder. Three rules make it safe:
1. Acquisition is `SET key token NX PX ttl` — one atomic command, so two
instances can never both believe they won.
2. The TTL (lockAtMostFor) means a crashed holder cannot wedge the lock
forever; it self-heals when the TTL lapses.
3. Release is token-checked in a Lua script, so a holder that outlived its
own TTL cannot delete a lock that now belongs to someone else.
`conn` is either a Redis URI string ("redis://host:6379"), a Carmine
conn map, or a java.util.Map treated as the Carmine :spec map (the shape a
Kotlin caller naturally produces: mapOf("uri" to "redis://...")).cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |