Class NeoRwLock

java.lang.Object
org.ores.async.NeoRwLock

public class NeoRwLock extends Object
An async (non-blocking) reader/writer lock. Sibling to NeoLock: same callback-shape acquire pattern (acquire returns an Unlock token via callback; release happens via that token), but supports two distinct holder modes:
  • Read — multiple concurrent holders allowed when no writer is held.
  • Write — exclusive. While a writer is held, no readers and no other writers are admitted.

Fairness policy: FIFO with reader-burst

Waiters are dispatched in arrival order. The one twist: when the lock is released and the head of the queue is a reader, all adjacent queued readers wake up concurrently. If the queue is [R1, R2, R3, W1, R4] and the lock becomes free, R1, R2, and R3 are all granted simultaneously. When all three release, W1 runs alone. When W1 releases, R4 proceeds.

This is the natural compromise:

  • No reader-preference (new readers don't jump ahead of queued writers — that would starve writers under heavy read load).
  • No writer-preference (queued writers don't block new readers when no writer is currently held — that would starve readers under bursty write workloads).
  • Adjacent queued readers run together to capture the spirit of "many readers concurrent" when they happen to be queued together.

One consequence: a single writer behind 1 000 queued readers waits until all 1 000 release. If you can't accept that, use tryAcquireWrite() or acquireWrite(long, Asyncc.IAsyncCallback) with a bounded timeout.

Usage

   NeoRwLock cacheLock = new NeoRwLock("config-cache");

   // Reader — multiple readers can hold the lock concurrently.
   cacheLock.acquireRead((err, unlock) -> {
       try {
           return cache.get(key);
       } finally {
           unlock.releaseLock();
       }
   });

   // Writer — exclusive.
   cacheLock.acquireWrite((err, unlock) -> {
       try {
           cache.put(key, value);
       } finally {
           unlock.releaseLock();
       }
   });

   // Sync helper for read-only critical sections — auto-releases even if the body throws.
   cacheLock.withRead(() -> renderTemplate(cache));

   // Sync helper for exclusive critical sections.
   cacheLock.withWrite(() -> cache.refresh());
 

What's not supported

  • Reader-to-writer upgrade — a holder of a read lock that wants to upgrade to a write lock must release the read first, then re-acquire as a writer. Two concurrent readers both trying to upgrade is a classic deadlock; Java's ReentrantReadWriteLock doesn't support upgrade either.
  • Writer-to-reader downgrade — similarly, release the write first. (The semantics are easier than upgrade but the API surface isn't worth it for v0.2.6.)
  • Reentrance — calling any acquire* from inside an acquire callback that has not yet been released will queue the new request behind the current holder. There is no general way to detect this in an async/callback world (release may fire on a different thread). Use tryAcquireRead() / tryAcquireWrite() if you need a defensive check.

Concurrency contract

  • Mutual exclusion: no read holder while a write is held; no write holder while any read is held.
  • FIFO across modes: writes never starve readers and vice versa; once a writer is at the head of the queue, no new readers are admitted until it gets a turn.
  • Reader-burst: adjacent queued readers wake up concurrently.
  • Timeouts cleanly remove the waiter from the queue without disturbing any other waiter or in-flight grant.
  • Double-release detection: releasing the same Unlock twice logs an IllegalStateException to System.err but does not throw past the release call site (which is typically a finally{} block).
Since:
0.2.6
See Also:
  • Constructor Details

    • NeoRwLock

      public NeoRwLock()
    • NeoRwLock

      public NeoRwLock(String namespace)
  • Method Details

    • getNamespace

      public String getNamespace()
    • readerCount

      public int readerCount()
      Snapshot of the number of currently-held read locks.
    • isWriteHeld

      public boolean isWriteHeld()
      Snapshot of whether a write lock is currently held.
    • queueDepth

      public int queueDepth()
      Snapshot of the number of waiters queued behind the current holder(s).
    • tryAcquireRead

      public Optional<Unlock> tryAcquireRead()
      Non-blocking attempt to acquire a read lock. Returns an Unlock if the lock was available and there are no queued waiters (to preserve FIFO fairness), or an empty Optional otherwise.
    • tryAcquireWrite

      public Optional<Unlock> tryAcquireWrite()
      Non-blocking attempt to acquire a write lock. Returns an Unlock if the lock was fully free (no readers, no writer, no queued waiters), or an empty Optional otherwise.
    • acquireRead

      public void acquireRead(Asyncc.IAsyncCallback<Unlock,Object> cb)
      Acquire a read lock. If the lock is currently free of writers and no waiter is queued, the callback fires immediately with the Unlock token. Otherwise the request is queued in arrival order.
    • acquireWrite

      public void acquireWrite(Asyncc.IAsyncCallback<Unlock,Object> cb)
      Acquire a write lock. If no readers are held, no writer is held, and no waiter is queued, the callback fires immediately. Otherwise the request is queued.
    • acquireRead

      public void acquireRead(long timeoutMs, Asyncc.IAsyncCallback<Unlock,Object> cb)
      Acquire a read lock with a bounded wait. If not granted within timeoutMs, fires the callback with a TimeoutException and cleanly removes the pending waiter from the queue.

      If timeoutMs <= 0, equivalent to tryAcquireRead(): succeeds immediately if available, otherwise fails with TimeoutException.

    • acquireWrite

      public void acquireWrite(long timeoutMs, Asyncc.IAsyncCallback<Unlock,Object> cb)
      Acquire a write lock with a bounded wait. See acquireRead(long, Asyncc.IAsyncCallback).
    • withRead

      public void withRead(Runnable criticalSection)
      Acquire a read lock, run the (synchronous) criticalSection, release. The lock is released even if the critical section throws.

      For async critical sections, use acquireRead(Asyncc.IAsyncCallback) directly so you can call Unlock.releaseLock() at the right point in your callback chain.

    • withWrite

      public void withWrite(Runnable criticalSection)
      Acquire a write lock, run the (synchronous) criticalSection, release. The lock is released even if the critical section throws.