Usage¶
Builder parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
clientId |
String |
Yes | Unique identifier for the calling service. Lock IDs are scoped as clientId#lockId, so different clients can lock the same entity independently. |
farmId |
String |
Yes | Data center / farm identifier. Used in key construction for DC-level locks. |
lockBase |
LockBase |
Yes | The lock engine — wraps the storage backend and lock mode. |
Initialize lock manager¶
DistributedLockManager lockManager = DistributedLockManager.builder()
.clientId("CLIENT_ID")
.farmId("FA1")
.lockBase(LockBase.builder()
.mode(LockMode.EXCLUSIVE)
.lockStore(AerospikeStore.builder()
.aerospikeClient(aerospikeClient)
.namespace("NAMESPACE")
.setSuffix("distributed_lock")
.build())
.build())
.build();
lockManager.initialize();
DistributedLockManager lockManager = DistributedLockManager.builder()
.clientId("CLIENT_ID")
.farmId("FA1")
.lockBase(LockBase.builder()
.mode(LockMode.EXCLUSIVE)
.lockStore(HBaseStore.builder()
.connection(connection)
.tableName("table_name")
.build())
.build())
.build();
lockManager.initialize();
LockConfiguration config = LockConfiguration.builder()
.lockTtl(Duration.ofSeconds(30))
.waitForLock(Duration.ofSeconds(10))
.sleepBetweenRetries(Duration.ofMillis(500))
.build();
DistributedLockManager lockManager = DistributedLockManager.builder()
.clientId("CLIENT_ID")
.farmId("FA1")
.lockBase(LockBase.builder()
.mode(LockMode.EXCLUSIVE)
.lockConfiguration(config)
.lockStore(AerospikeStore.builder()
.aerospikeClient(aerospikeClient)
.namespace("NAMESPACE")
.setSuffix("distributed_lock")
.build())
.build())
.build();
lockManager.initialize();
Warning
Always call lockManager.initialize() before any lock operations.
For HBase, this creates the table if it does not exist.
Configuring lock timing¶
By default, LockBase uses the library's built-in timing constants from LockConfiguration. You can override any or all of them by supplying a custom LockConfiguration to the LockBase builder.
LockConfiguration parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
lockTtl |
Duration |
90 seconds | How long the lock is held before the storage layer expires it automatically. |
waitForLock |
Duration |
90 seconds | Maximum time a blocking acquireLock call waits for a contended lock. |
sleepBetweenRetries |
Duration |
1000 ms | Sleep interval between successive acquisition attempts when a lock is unavailable. |
Default configuration¶
Omitting lockConfiguration(...) from the builder is fully supported — LockBase internally creates a default LockConfiguration with all defaults applied:
// These two are equivalent:
LockBase.builder().mode(LockMode.EXCLUSIVE).lockStore(store).build();
LockBase.builder().mode(LockMode.EXCLUSIVE).lockStore(store)
.lockConfiguration(LockConfiguration.builder().build()) // all defaults
.build();
Custom configuration examples¶
LockConfiguration config = LockConfiguration.builder()
.lockTtl(Duration.ofSeconds(30)) // short-lived locks
.waitForLock(Duration.ofSeconds(10)) // fail fast on contention
.sleepBetweenRetries(Duration.ofMillis(500)) // poll twice as fast
.build();
LockConfiguration config = LockConfiguration.builder()
.lockTtl(Duration.ofMinutes(10)) // hold lock for batch duration
.waitForLock(Duration.ofMinutes(5)) // willing to wait longer
.sleepBetweenRetries(Duration.ofSeconds(5)) // poll less frequently
.build();
// waitForLock and sleepBetweenRetries keep their defaults (90s and 1000ms)
LockConfiguration config = LockConfiguration.builder()
.lockTtl(Duration.ofSeconds(60))
.build();
How timing flows through the API
When you call acquireLock(lock) without explicit duration/timeout arguments, the values are read from LockConfiguration:
tryAcquireLock(lock)→ useslockConfiguration.getLockTtl()as TTL.acquireLock(lock)→ useslockConfiguration.getLockTtl()as TTL andlockConfiguration.getWaitForLock()as timeout.acquireLock(lock, duration)→ uses the explicitdurationbutlockConfiguration.getWaitForLock()as timeout.acquireLock(lock, duration, timeout)→ uses both explicit values; config is not consulted.- Retry sleep always uses
lockConfiguration.getSleepBetweenRetries().
Get a lock instance¶
Lock lock = lockManager.getLockInstance("order-123", LockLevel.DC);
The returned Lock object contains:
- lockId — composed as
clientId#order-123. - lockLevel —
DCorXDC. - farmId — inherited from the manager.
- acquiredStatus — an
AtomicBooleantracking whether this instance currently holds the lock.
Info
The Lock object is lightweight and does not perform any I/O on creation.
Actual storage interaction happens only on acquire / release.
Acquiring locks¶
Non-blocking — tryAcquireLock¶
Attempts to acquire immediately. Throws DLMException with ErrorCode.LOCK_UNAVAILABLE if the lock is already held.
// Default TTL (90 seconds)
lockManager.tryAcquireLock(lock);
// Custom TTL
lockManager.tryAcquireLock(lock, Duration.ofSeconds(120));
Blocking — acquireLock¶
Retries in a loop (1-second intervals) until the lock is acquired or the timeout expires.
// Default TTL (90s) and default timeout (90s)
lockManager.acquireLock(lock);
// Custom TTL, default timeout (90s)
lockManager.acquireLock(lock, Duration.ofSeconds(30));
// Custom TTL and custom timeout
lockManager.acquireLock(lock, Duration.ofSeconds(30), Duration.ofSeconds(10));
Default values
These defaults come from LockConfiguration and can be overridden per LockBase instance.
See Configuring lock timing for details.
| Constant | Default |
|---|---|
DEFAULT_LOCK_TTL |
90 seconds |
DEFAULT_WAIT_FOR_LOCK |
90 seconds |
DEFAULT_SLEEP_BETWEEN_RETRIES |
1000 ms |
Releasing locks¶
boolean released = lockManager.releaseLock(lock);
- Returns
trueif the lock was held and successfully released. - Returns
falseif the lock was not held by this instance (i.e.acquiredStatuswas alreadyfalse).
Warning
Always release in a finally block to avoid lock leaks.
Error handling¶
All lock operations throw DLMException. Use getErrorCode() to distinguish failure reasons:
Lock lock = lockManager.getLockInstance("order-123", LockLevel.DC);
try {
lockManager.tryAcquireLock(lock, Duration.ofSeconds(60));
// critical section
} catch (DLMException e) {
switch (e.getErrorCode()) {
case LOCK_UNAVAILABLE -> log.warn("Lock held by another holder");
case CONNECTION_ERROR -> log.error("Storage backend unreachable", e);
case RETRIES_EXHAUSTED -> log.error("All retry attempts failed", e);
default -> log.error("Unexpected error", e);
}
} finally {
lockManager.releaseLock(lock);
}
See Error Codes for the full list.
Complete lifecycle example¶
// ── Setup (application startup) ──
DistributedLockManager lockManager = DistributedLockManager.builder()
.clientId("payment-service")
.farmId("dc1")
.lockBase(LockBase.builder()
.mode(LockMode.EXCLUSIVE)
.lockStore(AerospikeStore.builder()
.aerospikeClient(aerospikeClient)
.namespace("locks")
.setSuffix("distributed_lock")
.build())
.build())
.build();
lockManager.initialize();
// ── Use (request handling) ──
Lock lock = lockManager.getLockInstance("txn-456", LockLevel.DC);
try {
lockManager.acquireLock(lock, Duration.ofSeconds(30), Duration.ofSeconds(10));
processPayment("txn-456");
} catch (DLMException e) {
if (e.getErrorCode() == ErrorCode.LOCK_UNAVAILABLE) {
// another instance is processing this transaction
}
} finally {
lockManager.releaseLock(lock);
}
// ── Teardown (application shutdown) ──
lockManager.destroy();
Cleanup¶
When the application shuts down, call destroy() to close the underlying store connection and release resources:
lockManager.destroy();
- For Aerospike, this calls
aerospikeClient.close(). - For HBase, this calls
connection.close().
Danger
Failing to call destroy() may leave dangling connections to the storage backend.
Invoke it in a shutdown hook or your framework's lifecycle callback.