/* * JBoss, Home of Professional Open Source. * Copyright 2013, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.controller; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.AbstractQueuedSynchronizer; /** * Basic lock implementation using a permit value to allow reentrancy. The lock will only be released when all * participants which previously acquired the lock have called {@linkplain #unlock}. * * The lock supports two mutually exclusive modes, shared and exclusive. If shared locks are acquired and held * then the exclusive lock may not be acquired, and if the exclusive lock is held, the shared locks may not be acquired. * For an existing "permit holder" (operationId), the lock may be reentrantly re-acquired. * * @author Emanuel Muckenhuber * @author Ken Wills */ class ModelControllerLock { private final Sync sync = new Sync(); /** * Attempts to acquire in exclusive mode. This will allow any other consumers using the same {@code permit} to * also acquire. This is typically used for a write lock. * @param permit - the permit Integer for this operation. May not be {@code null}. * @throws IllegalStateException - if the permit is null. */ void lock(final Integer permit) { if (permit == null) { throw new IllegalArgumentException(); } sync.acquire(permit); } /** * Attempts to acquire the lock in shared mode. In this mode the lock may be shared over a number * of different permit holders, and blocking the exclusive look from being acquired. Typically used for read locks to * allow multiple readers concurrently. * @param permit - the permit Integer for this operation. May not be {@code null}. */ void lockShared(final Integer permit) { if (permit == null) { throw new IllegalArgumentException(); } sync.acquireShared(permit); } /** Attempts exclusive acquisition with a max wait time. * @param permit - the permit Integer for this operation. May not be {@code null}. * @param timeout - the time value to wait for acquiring the lock * @param unit - See {@code TimeUnit} for valid values * @return {@code boolean} true on success. */ boolean lock(final Integer permit, final long timeout, final TimeUnit unit) { boolean result = false; try { result = lockInterruptibly(permit, timeout, unit); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return result; } /** Attempts shared acquisition with a max wait time. * @param permit - the permit Integer for this operation. May not be {@code null}. * @param timeout - the time value to wait for acquiring the lock * @param unit - See {@code TimeUnit} for valid values * @return {@code boolean} true on success. */ boolean lockShared(final Integer permit, final long timeout, final TimeUnit unit) { boolean result = false; try { result = lockSharedInterruptibly(permit, timeout, unit); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return result; } /** * Acquire the exclusive lock allowing the acquisition to be interrupted. * @param permit - the permit Integer for this operation. May not be {@code null}. * @throws InterruptedException - if the acquiring thread is interrupted. * @throws IllegalArgumentException if {@code permit} is null. */ void lockInterruptibly(final Integer permit) throws InterruptedException { if (permit == null) { throw new IllegalArgumentException(); } sync.acquireInterruptibly(permit); } /** * Acquire the shared lock allowing the acquisition to be interrupted. * @param permit - the permit Integer for this operation. May not be {@code null}. * @throws InterruptedException - if the acquiring thread is interrupted. * @throws IllegalArgumentException if {@code permit} is null. */ void lockSharedInterruptibly(final Integer permit) throws InterruptedException { if (permit == null) { throw new IllegalArgumentException(); } sync.acquireSharedInterruptibly(permit); } /** * Acquire the exclusive lock, with a max wait timeout to acquire. * @param permit - the permit Integer for this operation. May not be {@code null}. * @param timeout - the timeout scalar quantity. * @param unit - see {@code TimeUnit} for quantities. * @return {@code boolean} true on successful acquire. * @throws InterruptedException - if the acquiring thread was interrupted. * @throws IllegalArgumentException if {@code permit} is null. */ boolean lockInterruptibly(final Integer permit, final long timeout, final TimeUnit unit) throws InterruptedException { if (permit == null) { throw new IllegalArgumentException(); } return sync.tryAcquireNanos(permit, unit.toNanos(timeout)); } /** * Acquire the shared lock, with a max wait timeout to acquire. * @param permit - the permit Integer for this operation. May not be {@code null}. * @param timeout - the timeout scalar quantity. * @param unit - see {@code TimeUnit} for quantities. * @return {@code boolean} true on successful acquire. * @throws InterruptedException - if the acquiring thread was interrupted. * @throws IllegalArgumentException if {@code permit} is null. */ boolean lockSharedInterruptibly(final Integer permit, final long timeout, final TimeUnit unit) throws InterruptedException { if (permit == null) { throw new IllegalArgumentException(); } return sync.tryAcquireSharedNanos(permit, unit.toNanos(timeout)); } /** * Unlock a previously held exclusive lock. In the case of multiple lock holders, the underlying lock is only * released when all of the holders have called #unlock. * @param permit - the permit Integer for this operation. May not be {@code null}. * @throws IllegalArgumentException if {@code permit} is null. */ void unlock(final Integer permit) { if (permit == null) { throw new IllegalArgumentException(); } sync.release(permit); } /** * Unlock a previously held shared lock. In the case of multiple lock holders, the underlying lock is only * released when all of the holders have called #unlock. In the case of shared mode lock holders, they may * be a variety of different permit holders, as the shared mode lock is not tagged with a single owner as * the exclusive lock is. * @param permit - the permit Integer for this operation. May not be {@code null}. * @throws IllegalArgumentException if {@code permit} is null. */ void unlockShared(final Integer permit) { if (permit == null) { throw new IllegalArgumentException(); } sync.releaseShared(permit); } /** * Attempt to query and acquire the exclusive lock * @param permit - the permit Integer for this operation. May not be {@code null}. * @return {@code boolean} true if the lock was acquired, false if not available * for locking in exclusive mode, or already locked shared. */ boolean detectDeadlockAndGetLock(final int permit) { return sync.tryAcquire(permit); } /** * Implementation {@link AbstractQueuedSynchronizer} that maintains * lock state in a single {@code int}, managed by #getState() and #compareAndSet(). */ private class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 1L; // reserves the top 16 bytes for exclusive / shared mode short private static final int EXCLUSIVE_SHIFT = 30; private static final int COUNT_MASK = (1 << EXCLUSIVE_SHIFT) - 1; private static final int MAX_COUNT = COUNT_MASK; // values for indicating shared / exclusive modes. private static final short NOT_LOCKED = 0; private static final short EXCLUSIVE = 1; private static final short SHARED = 2; // the current permit holder in exclusive mode. private int permitHolder; @Override protected final boolean tryAcquire(final int permit) { return internalAcquire(permit, true) == 1; } @Override protected final int tryAcquireShared(final int permit) { return internalAcquire(permit, false); } @Override protected final boolean tryRelease(final int permit) { return internalRelease(permit, true); } @Override protected final boolean tryReleaseShared(final int permit) { return internalRelease(permit, false); } private void setCurrentPermitHolder(final int permit) { this.permitHolder = permit; } private int getCurrentPermitHolder() { return permitHolder; } private int getCount(final int value) { return (value & COUNT_MASK); } private int getLockMode(final int value) { return ((value >>> EXCLUSIVE_SHIFT)); } // store creates the int state value, storing it as (short)lockMode(short)lockCount private int makeState(final int lockMode, final int count) { assert lockMode == EXCLUSIVE || lockMode == SHARED; assert count > 0; if (count < 0 || count > MAX_COUNT) throw new IllegalMonitorStateException("Maximum lock count exceeded."); int state = ((lockMode) << EXCLUSIVE_SHIFT) | ((count) & COUNT_MASK); // tmp asserts assert count == getCount(state); assert lockMode == getLockMode(state); return state; } /** * Attempt to acquire the lock in the specified lock mode. * * @param permit - the lock permit object, for exclusive locks, multiple acquires for the same permit are allowed. * @param exclusive - Whether to attempt to acquire the exclusive (true) or shared lock (false). * @return {@code int} < 0 for failure, > 0 for success. */ private int internalAcquire(final int permit, final boolean exclusive) { // loop until the CAS is successful and the state has been updated. for (; ; ) { // read the current state int state = getState(); int count = getCount(state); int mode = getLockMode(state); // can't acquire exclusive when already in shared mode. if (mode == SHARED && exclusive) return -1; // (1) If getCurrentPermitHolder is a stale read, then state has been updated // by another thread, which means either mode or count will no longer match // and the CAS below will fail until the next getState() above. // (2) If state has been reentrantly updated with the same operation id, then // count will have changed (either +/-), and the CAS will fail even if the permit // matches. // (3) If the lock mode is exclusive, then shared acquire is not // allowed. if (mode == EXCLUSIVE && (getCurrentPermitHolder() != permit || !exclusive)) return -1; short next = (short) (count + 1); // increase lock count if (next < 0) { throw new IllegalMonitorStateException("Maximum lock count exceeded."); } int newState = makeState(exclusive ? EXCLUSIVE : SHARED, next); if (compareAndSetState(state, newState)) { if (exclusive) setCurrentPermitHolder(permit); return 1; } } } /** * Attempt to release the lock in the specified lock mode. * @param permit - the lock permit value. * @param exclusive - Whether to attempt to release the lock in the the exclusive (true) or shared lock (false) modes. * @return {@code boolean} true for success. */ private boolean internalRelease(int permit, final boolean exclusive) { for (; ; ) { int state = getState(); int mode = getLockMode(state); int next = getCount(state) - 1; // decrease lock count if (next < 0) { throw new IllegalMonitorStateException("Lock count exceeded."); } switch (mode) { case NOT_LOCKED: throw new IllegalMonitorStateException(exclusive ? "Write Lock not held." : "Read Lock not held."); case EXCLUSIVE: if (! exclusive) throw new IllegalMonitorStateException("Read lock release not allowed in exclusive mode."); if (getCurrentPermitHolder() != permit) return false; break; case SHARED: if (exclusive) throw new IllegalMonitorStateException("Write lock release not allowed in shared mode."); break; default: throw new IllegalMonitorStateException("Unknown lock mode."); } int newState = (next == 0 ? 0 : makeState(exclusive ? EXCLUSIVE : SHARED, next)); if (compareAndSetState(state, newState)) { // don't need to reset permit, it'll be written to on the next exclusive lock acquire return next == 0; } } } } }