Class LinearizableLock

java.lang.Object
com.loomcache.server.cp.LinearizableLock

public final class LinearizableLock extends Object
WARNING: JVM-LOCAL ONLY — NOT a distributed lock.

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
  • Constructor Details

    • LinearizableLock

      public LinearizableLock(String lockName)
      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 null
      IllegalArgumentException - if lockName is empty
  • Method Details

    • setRaftNode

      public void setRaftNode(@Nullable RaftNodeApi raftNode)
      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 deterministic LinearizableLock.FenceSequencer configured.
      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

      public FencingToken lockWithToken(String sessionId)
      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

      public long tryLock(long timeout, TimeUnit unit) throws InterruptedException
      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 wait
      unit - 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 waiting
      NullPointerException - if unit is null
    • tryLockForOwner

      public long tryLockForOwner(String ownerId)
      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; -1 when 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 by ownerId.

      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

      public void unlockForOwner(String ownerId)
      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

      public void autoRelease(@Nullable String expectedSessionId)
      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

      public LinearizableLock.LockSnapshot 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

      public @Nullable String getLockHolderId()
      Gets the ID of the thread currently holding the lock.
      Returns:
      the lock holder thread name, or null if unlocked
    • getLockHolderSessionId

      public @Nullable String 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

      public boolean validateFencingToken(FencingToken token)
      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

      public @Nullable FencingToken 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

      public String toString()
      Overrides:
      toString in class Object