Repository navigation
Programmatic API
Locksmith registers two beans, in.riido.locksmith.lock.LockOperations and
in.riido.locksmith.semaphore.SemaphoreOperations. Inject them like any other bean.
@Service
public class ReportService {
private final LockOperations locks;
private final SemaphoreOperations semaphores;
public ReportService(LockOperations locks, SemaphoreOperations semaphores) {
this.locks = locks;
this.semaphores = semaphores;
}
public void rebuild(String id) {
try (LockHandle lock = locks.key("report:" + id)
.type(LockType.WRITE)
.waitTime(Duration.ofSeconds(5))
.acquire()) {
if (!lock.acquired()) {
return; // someone else is rebuilding it
}
// critical section
}
}
public void export() {
try (PermitHandle permit = semaphores.key("reports")
.permits(5)
.waitTime(Duration.ofSeconds(2))
.acquire()) {
if (permit.acquired()) {
// at most five of these run at once, across all instances
}
}
}
}| Call | Meaning |
|---|---|
key(String) |
starts an acquire; the key has no prefix, Locksmith adds <prefix>lock:, or <prefix>rwlock: for READ and WRITE
|
.type(LockType) |
defaults to REENTRANT
|
.waitTime(Duration) |
defaults to Duration.ZERO, try once; negative throws IllegalArgumentException
|
.leaseTime(Duration) |
a fixed lease, not renewed; not calling it means renewal while held; below one millisecond throws IllegalArgumentException
|
.acquire() |
tries to acquire and returns a LockHandle
|
isLocked(String key, LockType type) |
whether any thread on any instance holds it right now: for REENTRANT the lock, for READ any read lock, for WRITE the write lock |
| Call | Meaning |
|---|---|
key(String) |
starts an acquire; Locksmith adds <prefix>semaphore:
|
.permits(int) |
the permit count; required, at least one, otherwise acquire() throws LocksmithConfigurationException
|
.waitTime(Duration) |
defaults to Duration.ZERO, try once; negative throws IllegalArgumentException
|
.leaseTime(Duration) |
defaults to locksmith.semaphore.lease-time; below one millisecond throws IllegalArgumentException
|
.acquire() |
tries to acquire one permit and returns a PermitHandle
|
availablePermits(String key) |
the number of free permits right now, never below zero |
| Call | LockHandle |
PermitHandle |
|---|---|---|
acquired() |
whether the lock was acquired | whether a permit was acquired |
key() |
the full Redis key, for example locksmith:lock:report:7
|
the full Redis key |
permitId() |
- | the Redisson permit id, or null when not acquired |
close() |
releases the lock if acquired | releases the permit if acquired |
-
acquire()never throws for "not acquired". Checkacquired(). -
close()never throws. Closing an unacquired handle, or closing twice, does nothing. A failed release is logged as a WARN and the key expires on its own. - Use try-with-resources so the handle is always closed.
- Redisson exceptions, for example when Redis is unreachable, propagate unchanged from
acquire(),isLockedandavailablePermits. A Redis error is never reported as "not acquired".
-
A lock belongs to the thread that acquired it, not to its handle. Until the handle is closed, that thread takes the same key again at once, because the lock is reentrant. If you hand the handle to another thread, do not acquire the same key on the acquiring thread until the handle is closed: a pooled thread that serves the next request for that key would otherwise run it alongside the first job.
-
A handle can be closed on any thread.
close()waits for the release, even on an interrupted thread; on a Redisson I/O or timer thread it only starts the release, because waiting there can stall Redisson. -
acquire(), like any annotated method, refuses to run where it could stall Redisson and throwsIllegalStateExceptionbefore anything is sent to Redis:- always on a Redisson I/O thread (
redisson-netty-*) or the timer thread (redisson-timer-*), for example inside a callback of a Redisson async call. On an I/O thread the message is Redisson's own,Sync methods can't be invoked from async/rx/reactive listeners.isLockedandavailablePermitsrefuse these threads too; - with a
waitTimeon any other Redisson thread, such as a topic listener or a task of Redisson's executor service, because Redisson delivers the message that ends the wait on those threads. A try-once acquire works there.
Move such work to another thread, for example with
thenApplyAsync, or use awaitTimeof zero. Locksmith recognises Redisson's threads by their default names, as Redisson's own check does. If your RedissonConfigsets its owneventLoopGroup,nettyExecutororexecutor, Locksmith cannot recognise those threads, so keep Locksmith calls,close()included, off them yourself. - always on a Redisson I/O thread (
- If the thread is interrupted before or while waiting,
acquire()keeps the interrupt flag and returns an unacquired handle, and nothing is left taken in Redis. Only an acquire that completed before the interrupt could stop it returns an acquired handle; close it as usual. - Once the
RedissonClientis shutting down,close()stops waiting within about a second, because Redisson may never answer a release it already sent; the lock or permit then expires on its own. A release that Redisson refuses because it is shutting down logs one WARN line without a stack trace.