Class LinearizableLock
This implementation is JVM-local only. Two clients on different nodes CAN both acquire the "same" named lock simultaneously because each node maintains its own independent lock instance. Lock/unlock operations are NOT replicated through Raft consensus.
For true distributed mutual exclusion, all lock operations must be routed through the Raft consensus log. This is planned for a future release. Do NOT rely on this class for cross-node mutual exclusion.
What this class does provide:
- JVM-local thread-safe locking with a
Semaphore-based mutex. - Fencing tokens: each acquisition returns a monotonically increasing token (derived from Raft commit index when a RaftNode is set, or a local counter otherwise).
- Session-aware auto-release on session expiry.
- Lock metrics (acquisition count, hold time).
Example:
LinearizableLock lock = ...;
FencingToken token = lock.lockWithToken(sessionId);
try {
if (lock.validateFencingToken(token)) {
doWork();
}
} finally {
lock.unlock();
}
- Since:
- 1.0
-
Nested Class Summary
Nested Classes -
Constructor Summary
ConstructorsConstructorDescriptionLinearizableLock(String lockName) Creates a new LinearizableLock with the given name. -
Method Summary
Modifier and TypeMethodDescriptionvoidautoRelease(@Nullable String expectedSessionId) Auto-releases the lock when a session expires.booleanawaitAvailableForOwner(String ownerId, long timeout, TimeUnit unit) Waits until this lock is free or already held reentrantly byownerId.longGets the total number of times this lock has been acquired.longGets the average hold time for this lock in milliseconds.@Nullable FencingTokenGets the current fencing token (if lock is held).longgetFence()Gets the fencing token of the current lock holder.longGets the fence counter for this specific lock (for testing).static longGets the current value of this lock's fence counter.intGets the number of times the current thread holds the lock (for reentrancy).@Nullable StringGets the ID of the thread currently holding the lock.@Nullable StringGets the session ID of the current lock holder.longGets the total hold time for all acquisitions in milliseconds.booleanisLocked()Checks if the lock is currently held.booleanChecks if the lock is held by the current thread.longlock()Acquires the lock, blocking until successful.lockWithToken(String sessionId) Acquires the lock and returns a FencingToken.static longsetGlobalFenceFloor(long floor) Monotonically advances the shared fence counter floor.voidsetRaftNode(@Nullable RaftNodeApi raftNode) Sets the RaftNode associated with this lock.Captures the lock's restorable state under a single synchronization point.toString()longAttempts to acquire the lock within the given timeout.longtryLockForOwner(String ownerId) Attempts to acquire this lock for a remote CP owner without blocking the Raft state machine.voidunlock()Releases the lock.voidunlockForOwner(String ownerId) Releases a lock held by the given remote CP owner.booleanvalidateFencingToken(FencingToken token) Validates a fencing token to ensure it matches the current lock holder.
-
Constructor Details
-
LinearizableLock
Creates a new LinearizableLock with the given name.- Parameters:
lockName- the name of this lock (must not be null or empty)- Throws:
NullPointerException- if lockName is nullIllegalArgumentException- if lockName is empty
-
-
Method Details
-
setRaftNode
Sets the RaftNode associated with this lock. Fencing tokens are not derived from this node's local commit index; subsystem-managed locks must also have a deterministicLinearizableLock.FenceSequencerconfigured.- Parameters:
raftNode- the RaftNode to use, or null to fall back to JVM-local counter
-
lock
public long lock()Acquires the lock, blocking until successful.Returns a monotonically increasing fencing token that can be used to detect stale lock holders.
- Returns:
- the fencing token for this lock acquisition
-
lockWithToken
Acquires the lock and returns a FencingToken.The returned FencingToken can be validated using
validateFencingToken(FencingToken).- Parameters:
sessionId- the session ID acquiring the lock (may be null for sessionless acquisition)- Returns:
- the FencingToken for this lock acquisition
- Throws:
NullPointerException- if sessionId is null and token generation requires it
-
tryLock
Attempts to acquire the lock within the given timeout.Returns a fencing token if successful, or -1 if the timeout expired.
Note: tryLock is non-reentrant. If the current thread already holds the lock, this method returns -1 immediately without waiting.
- Parameters:
timeout- the maximum time to waitunit- the time unit of the timeout- Returns:
- the fencing token if successful, or -1 if timeout expired or current thread already holds the lock
- Throws:
InterruptedException- if interrupted while waitingNullPointerException- if unit is null
-
tryLockForOwner
Attempts to acquire this lock for a remote CP owner without blocking the Raft state machine.The wire path retries from the client when this method returns
-1. Blocking inside a committed Raft command would prevent a later release entry from applying.- Parameters:
ownerId- stable owner identifier carried by the client lock proxy- Returns:
- the fencing token when acquired;
-1when another owner holds the lock
-
awaitAvailableForOwner
public boolean awaitAvailableForOwner(String ownerId, long timeout, TimeUnit unit) throws InterruptedException Waits until this lock is free or already held reentrantly byownerId.This helper never acquires the lock. Leaders use it before appending another CP_LOCK_ACQUIRE command, which keeps contended clients from repeatedly adding failed acquire attempts to the Raft log while another owner still holds the lock.
- Returns:
- true when a caller should retry acquire now; false if the wait timed out
- Throws:
InterruptedException- if interrupted while waiting
-
unlockForOwner
Releases a lock held by the given remote CP owner.- Parameters:
ownerId- stable owner identifier that acquired the current fencing token- Throws:
LockOwnershipLostException- if the lock is free or held by another owner
-
unlock
public void unlock()Releases the lock.Only the thread that acquired the lock should release it. Releasing without holding the lock throws an exception.
- Throws:
IllegalMonitorStateException- if the current thread does not hold the lock
-
autoRelease
Auto-releases the lock when a session expires. This is called by the SessionManager during session cleanup.- Parameters:
expectedSessionId- the session ID that is expected to hold the lock; release is skipped if the current holder's session differs (prevents TOCTOU race where another session acquired between the external check and this call)
-
getFence
public long getFence()Gets the fencing token of the current lock holder.Returns -1 if the lock is not currently held.
- Returns:
- the fencing token, or -1 if unlocked
-
isLocked
public boolean isLocked()Checks if the lock is currently held.- Returns:
- true if the lock is owned by some thread
-
snapshotState
Captures the lock's restorable state under a single synchronization point.- Returns:
- a consistent snapshot of held-state metadata
-
isLockedByCurrentThread
public boolean isLockedByCurrentThread()Checks if the lock is held by the current thread.- Returns:
- true if the current thread holds the lock
-
getLockHolderId
Gets the ID of the thread currently holding the lock.- Returns:
- the lock holder thread name, or null if unlocked
-
getLockHolderSessionId
Gets the session ID of the current lock holder.- Returns:
- the lock holder session ID, or null if the lock is unlocked or was not acquired with a session
-
getHoldCount
public int getHoldCount()Gets the number of times the current thread holds the lock (for reentrancy).This lock supports reentrancy — the owning thread can acquire it multiple times. This method returns the hold count.
- Returns:
- the hold count, or 0 if the current thread does not hold the lock
-
getGlobalFenceCounter
public static long getGlobalFenceCounter()Gets the current value of this lock's fence counter.- Returns:
- the fence counter value for this lock instance
-
setGlobalFenceFloor
public static long setGlobalFenceFloor(long floor) Monotonically advances the shared fence counter floor.- Parameters:
floor- the minimum counter value to preserve- Returns:
- the resulting global counter value
-
getFenceCounter
public long getFenceCounter()Gets the fence counter for this specific lock (for testing).- Returns:
- the highest fence value acquired by this lock
-
validateFencingToken
Validates a fencing token to ensure it matches the current lock holder.- Parameters:
token- the FencingToken to validate (must not be null)- Returns:
- true if the token is current and valid for this lock
- Throws:
NullPointerException- if token is null
-
getCurrentToken
Gets the current fencing token (if lock is held).- Returns:
- the current FencingToken, or null if lock is not held
-
getAcquisitionCount
public long getAcquisitionCount()Gets the total number of times this lock has been acquired.- Returns:
- the acquisition count
-
getAverageHoldTimeMs
public long getAverageHoldTimeMs()Gets the average hold time for this lock in milliseconds.- Returns:
- average hold time, or 0 if never acquired
-
getTotalHoldTimeMs
public long getTotalHoldTimeMs()Gets the total hold time for all acquisitions in milliseconds.- Returns:
- total hold time
-
toString
-