package org.infinispan.persistence.async; import java.util.concurrent.locks.AbstractQueuedSynchronizer; /** * A custom reader-writer-lock combined with a bounded buffer size counter. * <p/> * Supports multiple concurrent writers and a single exclusive reader. This ensures that no more * data is being written to the current state when the AsyncStoreCoordinator thread hands the * data off to the back-end store. * <p/> * Additionally, {@link #writeLock(int)} blocks if the buffer is full, and {@link #readLock()} * blocks if no data is available. * <p/> * This lock implementation is <em>not</em> reentrant! * * @author Karsten Blees */ class BufferLock { /** * AQS state is the number of 'items' in the buffer. AcquireShared blocks if the buffer is * full (>= size). */ private static class Counter extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 1688655561670368887L; private final int size; Counter(int size) { this.size = size; } int add(int count) { for (;;) { int state = getState(); if (compareAndSetState(state, state + count)) return state + count; } } @Override protected int tryAcquireShared(int count) { for (;;) { int state = getState(); if (state >= size) return -1; if (compareAndSetState(state, state + count)) return state + count >= size ? 0 : 1; } } @Override protected boolean tryReleaseShared(int state) { setState(state); return state < size; } } /** * AQS state is 0 if no data is available, 1 otherwise. AcquireShared blocks if no data is * available. */ private static class Available extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 6464514100313353749L; @Override protected int tryAcquireShared(int unused) { return getState() > 0 ? 1 : -1; } @Override protected boolean tryReleaseShared(int state) { setState(state > 0 ? 1 : 0); return state > 0; } } /** * Minimal non-reentrant read-write-lock. AQS state is number of concurrent shared locks, or 0 * if unlocked, or -1 if locked exclusively. */ private static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 2983687000985096017L; @Override protected boolean tryAcquire(int unused) { if (!compareAndSetState(0, -1)) return false; setExclusiveOwnerThread(Thread.currentThread()); return true; } @Override protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } @Override protected int tryAcquireShared(int unused) { for (;;) { int state = getState(); if (state < 0) return -1; if (compareAndSetState(state, state + 1)) return 1; } } @Override protected boolean tryReleaseShared(int unused) { for (;;) { int state = getState(); if (compareAndSetState(state, state - 1)) return true; } } } private final Sync sync; private final Counter counter; private final Available available; /** * Create a new BufferLock with the specified buffer size. * * @param size * the buffer size */ BufferLock(int size) { sync = new Sync(); counter = size > 0 ? new Counter(size) : null; available = new Available(); } /** * Acquires the write lock and consumes the specified amount of buffer space. Blocks if the * object is currently locked for reading, or if the buffer is full and count is greater than 0. * * @param count * number of items the caller intends to write */ void writeLock(int count) { if (count > 0 && counter != null) counter.acquireShared(count); sync.acquireShared(1); } /** * Releases the write lock. */ void writeUnlock() { sync.releaseShared(1); available.releaseShared(1); } /** * Acquires the read lock. Blocks if the buffer is empty or if the object is currently locked * for writing. */ void readLock() { available.acquireShared(1); sync.acquire(1); } /** * Releases the read lock. */ void readUnlock() { sync.release(1); } /** * Resets the buffer counter to the specified number. * * @param count * number of available items in the buffer */ void reset(int count) { if (counter != null) counter.releaseShared(count); available.releaseShared(count); } /** * Modifies the buffer counter by the specified value. * * @param count * number of items to add to the buffer counter */ void add(int count) { if (counter != null) count = counter.add(count); available.releaseShared(count); } }