/* * 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 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 of a transaction. Calls on behalf of a * locker should only be made from a single thread at a time. All {@link * Locker} objects supplied to this class should be instances of {@link * TxnLocker}. <p> * * This implementation checks for deadlock whenever a lock request is blocked * due to a conflict. It selects as the deadlock victim the locker with the * latest requested start time. The implementation does not deny requests that * would not result in deadlock. When requests block, it services the requests * in the order that they arrive. <p> * * This class and its {@linkplain LockManager superclass} use 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> * * @param <K> the type of key */ public final class TxnLockManager<K> extends LockManager<K> { /* -- Public 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 TxnLockManager(long lockTimeout, int numKeyMaps) { super(lockTimeout, numKeyMaps); } /* -- Public methods -- */ /** * {@inheritDoc} * * @throws IllegalArgumentException {@inheritDoc}, or if {@code locker} is * not an instance of {@link TxnLocker} */ @Override public LockConflict<K> lock(Locker<K> locker, K key, boolean forWrite) { checkTxnLocker(locker); return super.lock(locker, key, forWrite); } /** * {@inheritDoc} * * @throws IllegalArgumentException {@inheritDoc}, or if {@code locker} is * not an instance of {@link TxnLocker} */ @Override public LockConflict<K> lockNoWait( Locker<K> locker, K key, boolean forWrite) { checkTxnLocker(locker); return super.lockNoWait(locker, key, forWrite); } /** * {@inheritDoc} * * @throws IllegalArgumentException {@inheritDoc}, or if {@code locker} is * not an instance of {@link TxnLocker} */ public LockConflict<K> waitForLock(Locker<K> locker) { checkTxnLocker(locker); return super.waitForLock(locker); } /* -- Package access methods -- */ /** * {@inheritDoc} <p> * * This implementation calls the deadlock checker if the request blocks. * * @throws IllegalStateException {@inheritDoc} */ @Override LockConflict<K> lockNoWaitInternal( Locker<K> locker, K key, boolean forWrite) { LockConflict<K> conflict = super.lockNoWaitInternal(locker, key, forWrite); if (conflict != null) { if (logger.isLoggable(FINEST)) { logger.log(FINEST, "lock attempt {0}, {1}, forWrite:{2}" + "\n returns blocked -- checking for deadlocks", locker, key, forWrite); } LockConflict<K> deadlockConflict = new DeadlockChecker((TxnLocker<K>) locker).check(); if (deadlockConflict != null) { conflict = deadlockConflict; } } return conflict; } /* -- Other methods -- */ /** Throws IllegalArgumentException if the argument is not a TxnLocker. */ private static void checkTxnLocker(Locker<?> locker) { if (locker != null && !(locker instanceof TxnLocker<?>)) { throw new IllegalArgumentException("Locker is not a TxnLocker"); } } /* -- Other classes -- */ /** Utility class for detecting deadlocks. */ private class DeadlockChecker { /** * Maps lockers to information about lockers they are waiting for. * This map serves as a cache for information about lock owners, to * avoid the synchronization needed to retrieve it again when checking * for multiple deadlocks. */ private final Map<TxnLocker<K>, WaiterInfo<K>> waiterMap = new HashMap<TxnLocker<K>, WaiterInfo<K>>(); /** The top level locker we are checking for deadlocks. */ private final TxnLocker<K> rootLocker; /** * The pass number of the current deadlock check. There could be * multiple deadlocks active simultaneously, so deadlock checking is * repeated until no deadlocks are found. */ private int pass; /** The locker that was found in a circular reference. */ private TxnLocker<K> cycleBoundary; /** The current choice of a locker to abort. */ private TxnLocker<K> victim; /** Another locker in the deadlock. */ private TxnLocker<K> conflict; /** * Creates an instance of this class. * * @param locker the locker to check */ DeadlockChecker(TxnLocker<K> locker) { assert locker != null; rootLocker = locker; } /** * Checks for a deadlock starting with the root locker. * * @return a lock conflict if a deadlock was found, else {@code * null} */ LockConflict<K> check() { LockConflict<K> result = null; for (pass = 1; true; pass++) { if (!checkInternal(rootLocker, getWaiterInfo(rootLocker))) { if (result == null) { logger.log(FINEST, "check deadlock {0}: no deadlock", rootLocker); return null; } else { return result; } } if (logger.isLoggable(FINER)) { logger.log(FINER, "check deadlock {0}: victim {1}", rootLocker, victim); } LockConflict<K> deadlock = new LockConflict<K>(LockConflictType.DEADLOCK, conflict); getWaiterInfo(victim).waitingFor = null; victim.setConflict(deadlock); if (victim == rootLocker) { return deadlock; } else { result = new LockConflict<K>( LockConflictType.BLOCKED, conflict); } } } /** * Checks for deadlock starting with the specified locker and * information about its waiters. Returns whether a deadlock was * found. */ private boolean checkInternal( TxnLocker<K> locker, WaiterInfo<K> waiterInfo) { waiterInfo.pass = pass; for (LockRequest<K> request : waiterInfo.waitingFor) { TxnLocker<K> owner = (TxnLocker<K>) request.getLocker(); if (owner == locker) { if (logger.isLoggable(FINEST)) { logger.log(FINEST, "checking deadlock {0}, pass {1}:" + " locker {2}, waiting for {3}:" + " ignore self-reference", rootLocker, pass, locker, request); } } else { WaiterInfo<K> ownerInfo = getWaiterInfo(owner); if (ownerInfo.waitingFor == null) { if (logger.isLoggable(FINEST)) { logger.log(FINEST, "checking deadlock {0}, pass {1}:" + " locker {2}, waiting for {3}:" + " ignore not waiting", rootLocker, pass, locker, request); } } else if (ownerInfo.pass == pass) { /* Found a deadlock! */ cycleBoundary = owner; victim = owner; conflict = locker; if (logger.isLoggable(FINEST)) { logger.log(FINEST, "checking deadlock {0}, pass {1}:" + " locker {2}, waiting for {3}:" + " deadlock", rootLocker, pass, locker, request); } return true; } else { if (logger.isLoggable(FINEST)) { logger.log(FINEST, "checking deadlock {0}, pass {1}:" + " locker {2}, waiting for {3}:" + " recurse", rootLocker, pass, locker, request); } if (checkInternal(owner, ownerInfo)) { maybeUpdateVictim(owner); return true; } } } } return false; } /** * Returns information about the lockers that the specified locker is * waiting for. */ private WaiterInfo<K> getWaiterInfo(TxnLocker<K> locker) { WaiterInfo<K> waiterInfo = waiterMap.get(locker); if (waiterInfo == null) { List<LockRequest<K>> waitingFor; LockAttemptResult<K> result = locker.getWaitingFor(); if (result == null || locker.getConflict() != null) { waitingFor = null; } else { K key = result.request.getKey(); Map<K, Lock<K>> keyMap = getKeyMap(key); assert Lock.noteSync(TxnLockManager.this, key); try { synchronized (keyMap) { waitingFor = getLock(key, keyMap).copyOwners( TxnLockManager.this); } } finally { assert Lock.noteUnsync(TxnLockManager.this, key); } } waiterInfo = new WaiterInfo<K>(waitingFor); waiterMap.put(locker, waiterInfo); } return waiterInfo; } /** * Updates the victim and conflict fields to reflect an additional * locker in the deadlock chain. Use the argument as the victim if it * has a newer requested start time than the previously selected * victim. */ private void maybeUpdateVictim(TxnLocker<K> locker) { assert locker != null; if (conflict == null) { conflict = locker; } if (locker == cycleBoundary) { /* We've gone all the way around the circle, so we're done */ cycleBoundary = null; } else if (cycleBoundary != null && (locker.getRequestedStartTime() > victim.getRequestedStartTime())) { /* * We're still within the cycle and this locker started later * than the current victim, so use it instead. */ if (conflict == locker) { conflict = victim; } victim = locker; logger.log(FINEST, "checking deadlock {0}, pass {1}: new victim: {2}", rootLocker, pass, victim); } } } /** * Provides information about the requests a locker is waiting for. Used * in deadlock detection. */ private static class WaiterInfo<K> { /** * The requests the locker is waiting for, or {@code null} if not * waiting. */ List<LockRequest<K>> waitingFor; /** * The pass in which the locker was checked. If we encounter an * instance with the current pass number, then we've found a cycle. */ int pass = 0; /** * Creates an instance of this class. * * @param waitingFor the requests the locker is waiting for */ WaiterInfo(List<LockRequest<K>> waitingFor) { this.waitingFor = waitingFor; } } }