/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* --
*/
package com.sun.sgs.impl.util.lock;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import static com.sun.sgs.impl.sharedutil.Objects.uncheckedCast;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import static java.util.logging.Level.FINER;
import static java.util.logging.Level.FINEST;
import java.util.logging.Logger;
/**
* A class for managing lock conflicts. <p>
*
* This class uses the {@link Logger} named {@code com.sun.sgs.impl.util.lock}
* to log information at the following logging levels: <p>
*
* <ul>
* <li> {@link Level#FINER FINER} - Releasing locks; requesting, waiting for,
* and returning from lock requests
* <li> {@link Level#FINEST FINEST} - Notifying new lock owners, results of
* requesting locks before waiting, releasing locks, results of attempting
* to assign locks to waiters
* </ul> <p>
*
* The implementation of this class uses the following thread synchronization
* scheme to avoid internal deadlocks:
*
* <ul>
*
* <li>Synchronization is only used on {@link Locker} objects and on the {@code
* Map}s that hold {@link Lock} objects
*
* <li>A thread can synchronize on at most one locker and one lock at a time,
* always synchronizing on the locker first
*
* </ul>
*
* To make it easier to adhere to these rules, the implementation takes the
* following steps:
*
* <ul>
*
* <li>The {@code Lock} class is not synchronized <p>
*
* Callers of non-{@code Object} methods on the {@code Lock} class should
* make sure that they are synchronized on the associated key map.
*
* <li>The {@code Locker} class and its subclasses only use synchronization for
* getter and setter methods
*
* <li>Blocks synchronized on a {@code Lock} should not synchronize on anything
* else <p>
*
* The implementation enforces this requirement by having lock methods not
* make calls to other classes, and by performing minimal work while
* synchronized on the associated key map.
*
* <li>Blocks synchronized on a {@code Locker} should not synchronize on a
* different locker, but can synchronize on a {@code Lock}
*
* In fact, only one method synchronizes on a {@code Locker} and on a
* {@code Lock} -- the {@link #waitForLockInternal waitForLockInternal}
* method. That method also makes sure that the only synchronized {@code
* Locker} methods that it calls are on the locker it has already
* synchronized on.
*
* <li>Uses assertions to check adherence to the scheme
*
* </ul>
*
* @param <K> the type of key
*/
public class LockManager<K> {
/** The logger for this class. */
static final LoggerWrapper logger = new LoggerWrapper(
Logger.getLogger("com.sun.sgs.impl.util.lock"));
/**
* The maximum number of milliseconds to spend attempting to acquire a
* lock.
*/
private final long lockTimeout;
/**
* The number of separate maps to use for storing keys in order to support
* concurrent access.
*/
private final int numKeyMaps;
/**
* An array of maps from key to lock. The map to use is chosen by using
* the key's hash code mod the number of key maps. Synchronization for
* locks is based on locking the associated key map. Non-{@code Object}
* methods on locks should not be used without synchronizing on the
* associated key map lock.
*/
private final Map<K, Lock<K>>[] keyMaps;
/**
* When assertions are enabled, holds the {@code Locker} that the
* current thread is synchronized on, if any.
*/
final ThreadLocal<Locker<K>> currentLockerSync =
new ThreadLocal<Locker<K>>();
/**
* When assertions are enabled, hold the {@code Key} whose associated
* {@code Map} the current thread is synchronized on, if any.
*/
final ThreadLocal<K> currentKeySync = new ThreadLocal<K>();
/* -- Constructor -- */
/**
* Creates an instance of this class.
*
* @param lockTimeout the maximum number of milliseconds to acquire a
* lock
* @param numKeyMaps the number of separate maps to use for storing keys
* @throws IllegalArgumentException if {@code lockTimeout} or {@code
* numKeyMaps} is less than {@code 1}
*/
public LockManager(long lockTimeout, int numKeyMaps) {
if (lockTimeout < 1) {
throw new IllegalArgumentException(
"The lockTimeout must not be less than 1");
} else if (numKeyMaps < 1) {
throw new IllegalArgumentException(
"The numKeyMaps must not be less than 1");
}
this.lockTimeout = lockTimeout;
this.numKeyMaps = numKeyMaps;
keyMaps = uncheckedCast(new Map[numKeyMaps]);
for (int i = 0; i < numKeyMaps; i++) {
keyMaps[i] = new HashMap<K, Lock<K>>();
}
}
/* -- Public methods -- */
/**
* Attempts to acquire a lock, waiting if needed. Returns information
* about conflicts that occurred while attempting to acquire the lock that
* prevented the lock from being acquired, or else {@code null} if the lock
* was acquired. If the {@code type} field of the return value is {@link
* LockConflictType#DEADLOCK DEADLOCK}, then the caller should abort the
* transaction, and any subsequent lock or wait requests will throw {@code
* IllegalStateException}. Otherwise, the caller can repeat this call, and
* any conflicts from earlier calls will be ignored.
*
* @param locker the locker requesting the lock
* @param key the key identifying the lock
* @param forWrite whether to request a write lock
* @return lock conflict information, or {@code null} if there was no
* conflict
* @throws IllegalArgumentException if {@code locker} has a different lock
* manager
* @throws IllegalStateException if an earlier lock attempt for this
* transaction produced a deadlock, or if still waiting for an
* earlier attempt to complete
*/
public LockConflict<K> lock(Locker<K> locker, K key, boolean forWrite) {
LockConflict<K> conflict = lockNoWait(locker, key, forWrite);
return (conflict == null) ? null : waitForLockInternal(locker);
}
/**
* Attempts to acquire a lock, returning immediately. Returns information
* about any conflict that occurred while attempting to acquire the lock,
* or else {@code null} if the lock was acquired. If the attempt to
* acquire the lock was blocked, returns a value with a {@code type} field
* of {@link LockConflictType#BLOCKED BLOCKED} rather than waiting. If the
* {@code type} field of the return value is {@link
* LockConflictType#DEADLOCK DEADLOCK}, then the caller should abort the
* transaction, and any subsequent lock or wait requests will throw {@code
* IllegalStateException}. Otherwise, the caller can repeat this call, and
* any conflicts from earlier calls will be ignored.
*
* @param locker the locker requesting the lock
* @param key the key identifying the lock
* @param forWrite whether to request a write lock
* @return lock conflict information, or {@code null} if there was no
* conflict
* @throws IllegalArgumentException if {@code locker} has a different lock
* manager
* @throws IllegalStateException if an earlier lock attempt for this
* transaction produced a deadlock, or if still waiting for an
* earlier attempt to complete
*/
public LockConflict<K> lockNoWait(
Locker<K> locker, K key, boolean forWrite)
{
checkLockManager(locker);
return lockNoWaitInternal(locker, key, forWrite);
}
/**
* Waits for a previous attempt to obtain a lock that blocked. Returns
* information about any conflict that occurred while attempting to acquire
* the lock, or else {@code null} if the lock was acquired or the
* transaction was not waiting. If the {@code type} field of the return
* value is {@link LockConflictType#DEADLOCK DEADLOCK}, then the caller
* should abort the transaction, and any subsequent lock or wait requests
* will throw {@code IllegalStateException}.
*
* @param locker the locker requesting the lock
* @return lock conflict information, or {@code null} if there was no
* conflict
* @throws IllegalArgumentException if {@code locker} has a different lock
* manager
*/
public LockConflict<K> waitForLock(Locker<K> locker) {
checkLockManager(locker);
return waitForLockInternal(locker);
}
/**
* Releases a lock held by a locker. This method does nothing if the lock
* is not held.
*
* @param locker the locker holding the lock
* @param key the key identifying the lock
* @throws IllegalArgumentException if {@code locker} has a different lock
* manager
*/
public void releaseLock(Locker<K> locker, K key) {
if (logger.isLoggable(FINER)) {
logger.log(FINER, "release {0} {1}", locker, key);
}
releaseLockInternal(locker, key, false);
}
/**
* Returns a possibly read-only list that contains a snapshot of the
* current owners of a lock, as identified by lock requests.
*
* @param key the key identifying the lock
* @return a list of the requests
*/
public List<LockRequest<K>> getOwners(K key) {
Map<K, Lock<K>> keyMap = getKeyMap(key);
assert Lock.noteSync(this, key);
try {
synchronized (keyMap) {
return getLock(key, keyMap).copyOwners(this);
}
} finally {
assert Lock.noteUnsync(this, key);
}
}
/**
* Returns a possibly read-only list that contains a snapshot of the
* current waiters for a lock, as identified by lock requests.
*
* @param key the key identifying the lock
* @return a list of the requests
*/
public List<LockRequest<K>> getWaiters(K key) {
Map<K, Lock<K>> keyMap = getKeyMap(key);
assert Lock.noteSync(this, key);
try {
synchronized (keyMap) {
return getLock(key, keyMap).copyWaiters(this);
}
} finally {
assert Lock.noteUnsync(this, key);
}
}
/* -- Package access methods -- */
/**
* Returns the key map to use for the specified key.
*
* @param key the key
* @return the associated key map
*/
Map<K, Lock<K>> getKeyMap(K key) {
/* Mask off the sign bit to get a positive value */
int index = (key.hashCode() & Integer.MAX_VALUE) % numKeyMaps;
return keyMaps[index];
}
/**
* Returns the lock associated with the specified key from the key map,
* which should be the one returned by calling {@link #getKeyMap
* getKeyMap}. The lock on {@code keyMap} should be held.
*
* @param key the key
* @param keyMap the keyMap
* @return the associated lock
*/
Lock<K> getLock(K key, Map<K, Lock<K>> keyMap) {
assert Thread.holdsLock(keyMap);
Lock<K> lock = keyMap.get(key);
if (lock == null) {
lock = new Lock<K>(key);
keyMap.put(key, lock);
}
return lock;
}
/**
* Attempts to acquire a lock, returning immediately. Like {@link
* #lockNoWait}, but does not check that the correct lock manager was
* supplied.
*
* @param locker the locker requesting the lock
* @param key the key identifying the lock
* @param forWrite whether to request a write lock
* @return lock conflict information, or {@code null} if there was no
* conflict
* @throws IllegalStateException if an earlier lock attempt for this
* transaction produced a deadlock, or if still waiting for an
* earlier attempt to complete
*/
LockConflict<K> lockNoWaitInternal(
Locker<K> locker, K key, boolean forWrite)
{
if (locker.getWaitingFor() != null) {
throw new IllegalStateException(
"Attempt to obtain a new lock while waiting");
}
LockConflict<K> conflict = locker.getConflict();
if (conflict != null) {
if (conflict.type == LockConflictType.DEADLOCK) {
throw new IllegalStateException(
"Attempt to obtain a new lock after a deadlock");
} else {
/* Ignoring the previous conflict */
locker.clearConflict();
}
}
LockAttemptResult<K> result;
Map<K, Lock<K>> keyMap = getKeyMap(key);
assert Lock.noteSync(this, key);
try {
synchronized (keyMap) {
Lock<K> lock = getLock(key, keyMap);
result = lock.lock(locker, forWrite, false);
}
} finally {
assert Lock.noteUnsync(this, key);
}
if (result == null) {
if (logger.isLoggable(FINER)) {
logger.log(FINER,
"lock {0}, {1}, forWrite:{2}" +
"\n returns null (already granted)",
locker, key, forWrite);
}
return null;
}
if (result.conflict == null) {
if (logger.isLoggable(FINER)) {
logger.log(FINER,
"lock {0}, {1}, forWrite:{2}" +
"\n returns null (granted)",
locker, key, forWrite);
}
return null;
}
locker.setWaitingFor(result);
conflict = new LockConflict<K>(
LockConflictType.BLOCKED, result.conflict);
if (logger.isLoggable(FINER)) {
logger.log(FINER,
"lock {0}, {1}, forWrite:{2}\n returns {3}",
locker, key, forWrite, conflict);
}
return conflict;
}
/**
* Waits for a previous attempt to obtain a lock that blocked. Like {@link
* #waitForLock}, but does not check that the correct lock manager was
* supplied.
*
* @param locker the locker requesting the lock
* @return lock conflict information, or {@code null} if there was no
* conflict
*/
LockConflict<K> waitForLockInternal(Locker<K> locker) {
assert locker.noteSync();
try {
synchronized (locker) {
LockAttemptResult<K> result = locker.getWaitingFor();
if (result == null) {
logger.log(FINER, "lock {0}\n returns null (not waiting)",
locker);
return null;
}
Lock<K> lock;
K key = result.request.getKey();
Map<K, Lock<K>> keyMap = getKeyMap(key);
assert Lock.noteSync(this, key);
try {
synchronized (keyMap) {
lock = getLock(key, keyMap);
}
} finally {
assert Lock.noteUnsync(this, key);
}
long now = System.currentTimeMillis();
long stop = locker.getLockTimeoutTime(now, lockTimeout);
LockConflict<K> conflict = null;
while (true) {
if (conflict == null) {
conflict = locker.getConflict();
}
boolean isOwner;
boolean timedOut = false;
assert Lock.noteSync(this, key);
try {
synchronized (keyMap) {
isOwner = lock.isOwner(result.request);
if (!isOwner) {
if (conflict != null) {
lock.flushWaiter(locker);
} else if (now >= stop) {
timedOut = true;
lock.flushWaiter(locker);
}
}
}
} finally {
assert Lock.noteUnsync(this, key);
}
if (isOwner) {
locker.setWaitingFor(null);
locker.clearConflict();
if (logger.isLoggable(FINER)) {
logger.log(
FINER,
"lock {0}, {1}, forWrite:{2}" +
"\n returns null (granted)",
locker, key, result.request.getForWrite());
}
return null;
} else if (timedOut) {
conflict = new LockConflict<K>(
LockConflictType.TIMEOUT, result.conflict);
break;
} else if (conflict != null) {
break;
}
if (logger.isLoggable(FINER)) {
logger.log(FINER,
"wait for lock {0}, {1}, forWrite:{2}" +
", wait:{3,number,#}",
locker, key, result.request.getForWrite(),
stop - now);
}
try {
locker.wait(stop - now);
} catch (InterruptedException e) {
conflict = new LockConflict<K>(
LockConflictType.INTERRUPTED, result.conflict);
/* Loop again to check owners and waiters */
}
now = System.currentTimeMillis();
}
locker.setWaitingFor(null);
if (logger.isLoggable(FINER)) {
logger.log(FINER,
"lock {0}, {1}, forWrite:{2}\n returns {3}",
locker, key, result.request.getForWrite(),
conflict);
}
return conflict;
}
} finally {
assert locker.noteUnsync();
}
}
/**
* Releases a lock, but only downgrading it if {@code downgrade} is true.
* Like {@link #releaseLock}, but permits specifying if the lock is being
* downgraded rather than fully released.
*
* @param locker the locker holding the lock
* @param key the key identifying the lock
* @param downgrade whether the lock should only be downgraded
* @throws IllegalArgumentException if {@code locker} has a different lock
* manager
*/
void releaseLockInternal(Locker<K> locker, K key, boolean downgrade) {
checkLockManager(locker);
List<Locker<K>> newOwners = Collections.emptyList();
Map<K, Lock<K>> keyMap = getKeyMap(key);
assert Lock.noteSync(this, key);
try {
synchronized (keyMap) {
/* Don't create the lock if it isn't present */
Lock<K> lock = keyMap.get(key);
if (lock != null) {
newOwners = lock.release(locker, downgrade);
if (!lock.inUse(this)) {
keyMap.remove(key);
}
}
}
} finally {
assert Lock.noteUnsync(this, key);
}
for (Locker<K> newOwner : newOwners) {
logger.log(FINEST, "notify new owner {0}", newOwner);
assert newOwner.noteSync();
try {
synchronized (newOwner) {
newOwner.notifyAll();
}
} finally {
assert newOwner.noteUnsync();
}
}
}
/* -- Private methods -- */
/** Checks that the locker has this lock manager. */
private void checkLockManager(Locker<K> locker) {
if (locker.getLockManager() != this) {
throw new IllegalArgumentException(
"The locker has a different lock manager");
}
}
}