/** * Copyright 2005-2012 Akiban Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.persistit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.AbstractQueuedSynchronizer; import java.util.concurrent.locks.ReentrantReadWriteLock; import com.persistit.exception.PersistitInterruptedException; import com.persistit.util.Util; /** * Base class for objects that need synchronization across threads. The methods * {@link #claim(boolean)} and {@link #register()} establish and release either * exclusive or non-exclusive access to the object. Numerous other status flags * are also managed by this class, such as {@link #isDirty()} and * {@link #isValid()}. * <p> * The implementation of this class uses a {@link AbstractQueuedSynchronizer} * and is similar to {@link ReentrantReadWriteLock}. The synchronization policy * is a non-strict fair policy which is necessary and sufficient to prevent * starvation on busy system. * * See {@link Buffer}, {@link Tree} and {@link Volume}. * * @author peter */ abstract class SharedResource { /** * Default maximum time to wait for access to this resource. Methods throw * an InUseException when this time is exceeded. */ public final static long DEFAULT_MAX_WAIT_TIME = 60000L; /** * Mask for count of Threads holding a reader or writer claim (0-32767) */ final static int CLAIMED_MASK = 0x00007FFF; /** * Mask for count of Threads holding a writer claim (0 or 1) */ final static int WRITER_MASK = 0x00008000; /** * Mask for field indicating the resource needs to be written */ final static int DIRTY_MASK = 0x00010000; /** * Status field mask for valid bit. The bit is set if the contents of the * buffer accurately reflects the status of the page. */ final static int VALID_MASK = 0x00020000; /** * Status field mask for a resource that is dirty but not required to be * written with any checkpoint. */ final static int TEMPORARY_MASK = 0x00400000; /** * Status field mask indicating a resource has been touched. Used by * clock-based page replacement algorithm. */ final static int TOUCHED_MASK = 0x08000000; /** * Mask for bit field indicating that resource (a Buffer) should not be * replaced. The buffer houses a Volume's head page. */ final static int FIXED_MASK = 0x40000000; final static AtomicLong ACQUIRE_LOOPS = new AtomicLong(); final static AtomicLong RELEASE_LOOPS = new AtomicLong(); final static AtomicLong SET_BIT_LOOPS = new AtomicLong(); final static AtomicLong CLEAR_BIT_LOOPS = new AtomicLong(); /** * Extension of {@link AbstractQueuedSynchronizer} with Persistit semantics. */ private static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 1L; @Override protected boolean tryAcquire(final int arg) { assert arg == 1; // // Implement non-strict fairness doctrine, as suggested in // download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/AbstractQueuedSynchronizer.html // final Thread thisThread = Thread.currentThread(); for (;;) { final Thread queuedThread = getFirstQueuedThread(); if (queuedThread != null && queuedThread != thisThread && getExclusiveOwnerThread() != thisThread) { return false; } final int state = getState(); if (!isAvailable(state)) { return false; } else if (compareAndSetState(state, (state | WRITER_MASK) + 1)) { setExclusiveOwnerThread(thisThread); return true; } ACQUIRE_LOOPS.incrementAndGet(); } } @Override protected int tryAcquireShared(final int arg) { assert arg == 1; // // Implement non-strict fairness doctrine, as suggested in // download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/AbstractQueuedSynchronizer.html // final Thread thisThread = Thread.currentThread(); for (;;) { final Thread queuedThread = getFirstQueuedThread(); if (queuedThread != null && queuedThread != thisThread && getExclusiveOwnerThread() != thisThread) { return -1; } final int state = getState(); if (!isAvailableShared(state)) { return -1; } else if (compareAndSetState(state, state + 1)) { return CLAIMED_MASK - (state & CLAIMED_MASK) - 1; } ACQUIRE_LOOPS.incrementAndGet(); } } /** * Attempt to convert shared to exclusive. The caller must already have * acquired shared access. This method upgrades the state to exclusive, * but only if there is exactly one shared acquire. * * TODO - prove that caller already a reader claim * * @return */ private boolean tryUpgrade() { for (;;) { final int state = getState(); final Thread thisThread = Thread.currentThread(); if ((state & CLAIMED_MASK) != 1 || ((state & WRITER_MASK) != 0) && getExclusiveOwnerThread() != thisThread) { return false; } else if (compareAndSetState(state, state | WRITER_MASK)) { setExclusiveOwnerThread(thisThread); return true; } ACQUIRE_LOOPS.incrementAndGet(); } } @Override protected boolean tryRelease(final int arg) { return (releaseState(arg) & WRITER_MASK) == 0; } @Override protected boolean tryReleaseShared(final int arg) { return (releaseState(arg) & WRITER_MASK) == 0; } @Override protected boolean isHeldExclusively() { return (getState() & WRITER_MASK) != 0; } private boolean isAvailable(final int state) { return (state & CLAIMED_MASK) < CLAIMED_MASK && ((state & CLAIMED_MASK) == 0 || getExclusiveOwnerThread() == Thread.currentThread()); } private boolean isAvailableShared(final int state) { return (state & CLAIMED_MASK) < CLAIMED_MASK && ((state & WRITER_MASK) == 0 || getExclusiveOwnerThread() == Thread.currentThread()); } private int releaseState(final int count) { assert count == 0 || count == 1; for (;;) { final int state = getState(); if ((state & CLAIMED_MASK) == 1) { final int newState = (state - count) & ~WRITER_MASK; // Do this first so that another thread setting // a writer claim does not lose its copy. setExclusiveOwnerThread(null); if (compareAndSetState(state, newState)) { return newState; } } else if ((state & CLAIMED_MASK) > 1) { if (count == 0) { return state; } final int newState = state - 1; if (compareAndSetState(state, newState)) { return newState; } } else { throw new IllegalMonitorStateException("Unmatched attempt to release " + this); } RELEASE_LOOPS.incrementAndGet(); } } // Visible to outer class private int state() { return getState(); } private Thread writerThread() { return getExclusiveOwnerThread(); } private boolean setBitsInState(final int mask) { for (;;) { final int state = getState(); final int newState = state | mask; if (compareAndSetState(state, newState)) { return state != newState; } SET_BIT_LOOPS.incrementAndGet(); } } private boolean clearBitsInState(final int mask) { for (;;) { final int state = getState(); final int newState = state & ~mask; if (compareAndSetState(state, newState)) { return state != newState; } CLEAR_BIT_LOOPS.incrementAndGet(); } } private boolean testBitsInState(final int mask) { return (getState() & mask) != 0; } } protected final Persistit _persistit; private final Sync _sync = new Sync(); /** * A counter that increments every time the resource is changed. */ private final AtomicLong _generation = new AtomicLong(); protected SharedResource(final Persistit persistit) { _persistit = persistit; } public boolean isAvailable(final boolean writer) { return writer ? _sync.isAvailable(_sync.state()) : _sync.isAvailableShared(_sync.state()); } boolean isDirty() { return _sync.testBitsInState(DIRTY_MASK); } public boolean isValid() { return _sync.testBitsInState(VALID_MASK); } public boolean isTemporary() { return _sync.testBitsInState(TEMPORARY_MASK); } boolean isFixed() { return _sync.testBitsInState(FIXED_MASK); } boolean isWriter() { return _sync.testBitsInState(WRITER_MASK); } /** * Indicates whether this Thread has a writer claim on this page. * * @return <i>true</i> if this Thread has a writer claim on this page. */ boolean isOwnedAsWriterByMe() { return (_sync.writerThread() == Thread.currentThread()); } boolean isOwnedAsWriterByOther() { final Thread t = _sync.writerThread(); return t != null && t != Thread.currentThread(); } boolean claim(final boolean writer) throws PersistitInterruptedException { return claim(writer, DEFAULT_MAX_WAIT_TIME); } boolean claim(final boolean writer, final long timeout) throws PersistitInterruptedException { if (timeout == 0) { if (writer) { return _sync.tryAcquire(1); } else { return _sync.tryAcquireShared(1) >= 0; } } else { final long ns = Math.min(timeout, Long.MAX_VALUE / Util.NS_PER_MS) * Util.NS_PER_MS; try { if (writer) { if (_sync.tryAcquireNanos(1, ns)) { return true; } } else { if (_sync.tryAcquireSharedNanos(1, ns)) { return true; } } } catch (final InterruptedException e) { throw new PersistitInterruptedException(e); } return false; } } boolean upgradeClaim() { return _sync.tryUpgrade(); } void release() { _sync.release(1); } void releaseWriterClaim() { _sync.release(0); } boolean setDirty() { return _sync.setBitsInState(DIRTY_MASK); } boolean clearDirty() { return _sync.clearBitsInState(DIRTY_MASK); } void setTouched() { _sync.setBitsInState(TOUCHED_MASK); } void clearTouched() { _sync.clearBitsInState(TOUCHED_MASK); } void setFixed() { _sync.setBitsInState(FIXED_MASK); } void clearFixed() { _sync.clearBitsInState(FIXED_MASK); } boolean isTouched() { return _sync.testBitsInState(TOUCHED_MASK); } public long getGeneration() { return _generation.get(); } void setTemporary() { _sync.setBitsInState(TEMPORARY_MASK); } void clearTemporary() { _sync.clearBitsInState(TEMPORARY_MASK); } void setValid() { _sync.setBitsInState(VALID_MASK); } void clearValid() { _sync.clearBitsInState(VALID_MASK); } void bumpGeneration() { _generation.incrementAndGet(); } int getStatus() { return _sync.state(); } Persistit getPersistit() { return _persistit; } /** * Sets bits in the state. This method does not change the bits used by the * synchronizer to maintain lock state. * * @param mask */ void setStatus(final SharedResource resource) { final int mask = resource.getStatus(); _sync.clearBitsInState(~(WRITER_MASK | CLAIMED_MASK)); _sync.setBitsInState(mask & ~(WRITER_MASK | CLAIMED_MASK)); } public String getStatusCode() { return getStatusCode(getStatus()); } public String getStatusDisplayString() { final Thread writerThread = _sync.writerThread(); final int state = _sync.state(); if (writerThread == null) { return getStatusCode(state); } else { return getStatusCode(state) + " <" + writerThread.getName() + ">"; } } public static String getStatusCode(final int state) { // Common cases so we don't have to construct new StringBuilders switch (state) { case 0: return ""; case VALID_MASK: return "v"; case VALID_MASK | DIRTY_MASK: return "vd"; case VALID_MASK | 1: return "vr1"; case VALID_MASK | WRITER_MASK | 1: return "vwr1"; case VALID_MASK | DIRTY_MASK | 1: return "vdr1"; case VALID_MASK | WRITER_MASK | DIRTY_MASK | 1: return "vdwr1"; default: final StringBuilder sb = new StringBuilder(8); if ((state & VALID_MASK) != 0) { // TODO chars sb.append("v"); } if ((state & DIRTY_MASK) != 0) { sb.append("d"); } if ((state & TEMPORARY_MASK) != 0) { sb.append("t"); } if ((state & WRITER_MASK) != 0) { sb.append("w"); } if ((state & CLAIMED_MASK) != 0) { sb.append("r"); sb.append(state & CLAIMED_MASK); } return sb.toString(); } } public Thread getWriterThread() { return _sync.writerThread(); } @Override public String toString() { return getClass().getSimpleName() + this.hashCode() + " status=" + getStatusDisplayString(); } }