--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.services.locks.ConcurrentLockSet\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.services.locks;\r
+\r
+import org.apache.derby.iapi.services.locks.CompatibilitySpace;\r
+import org.apache.derby.iapi.services.locks.Latch;\r
+import org.apache.derby.iapi.services.locks.Lockable;\r
+import org.apache.derby.iapi.services.locks.C_LockFactory;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.services.diag.DiagnosticUtil;\r
+\r
+import org.apache.derby.iapi.reference.Property;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import java.util.concurrent.atomic.AtomicInteger;\r
+import java.util.concurrent.locks.Condition;\r
+import java.util.concurrent.locks.ReentrantLock;\r
+import java.util.concurrent.ConcurrentHashMap;\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.Enumeration;\r
+import java.util.Map;\r
+\r
+\r
+/**\r
+ A ConcurrentLockSet is a complete lock table which maps\r
+ <code>Lockable</code>s to <code>LockControl</code> objects.\r
+\r
+ <P>\r
+ A LockControl contains information about the locks held on a Lockable.\r
+\r
+ <BR>\r
+ MT - Mutable : All public methods of this class, except addWaiters, are\r
+ thread safe. addWaiters can only be called from the thread which performs\r
+ deadlock detection. Only one thread can perform deadlock detection at a\r
+ time.\r
+\r
+ <BR>\r
+ The class creates ActiveLock and LockControl objects.\r
+ \r
+ LockControl objects are never passed out of this class, All the methods of \r
+ LockControl are called while holding a ReentrantLock associated with the\r
+ Lockable controlled by the LockControl, thus providing the\r
+ single threading that LockControl required.\r
+\r
+ Methods of Lockables are only called by this class or LockControl, and \r
+ always while holding the corresponding ReentrantLock, thus providing the\r
+ single threading that Lockable requires.\r
+ \r
+ @see LockControl\r
+*/\r
+\r
+final class ConcurrentLockSet implements LockTable {\r
+ /*\r
+ ** Fields\r
+ */\r
+ private final AbstractPool factory;\r
+\r
+ /** Hash table which maps <code>Lockable</code> objects to\r
+ * <code>Lock</code>s. */\r
+ private final ConcurrentHashMap<Lockable, Entry> locks;\r
+\r
+ /**\r
+ * List containing all entries seen by the last call to\r
+ * <code>addWaiters()</code>. Makes it possible for the deadlock detection\r
+ * thread to lock all the entries it has visited until it has\r
+ * finished. This prevents false deadlocks from being reported (because all\r
+ * observed waiters must still be waiting when the deadlock detection has\r
+ * completed).\r
+ */\r
+ private ArrayList<Entry> seenByDeadlockDetection;\r
+\r
+ /**\r
+ Timeout for deadlocks, in ms.\r
+ <BR>\r
+ MT - immutable\r
+ */\r
+ private int deadlockTimeout = Property.DEADLOCK_TIMEOUT_DEFAULT * 1000;\r
+ private int waitTimeout = Property.WAIT_TIMEOUT_DEFAULT * 1000;\r
+\r
+//EXCLUDE-START-lockdiag- \r
+\r
+ // this varible is set and get without synchronization. \r
+ // Only one thread should be setting it at one time.\r
+ private boolean deadlockTrace;\r
+\r
+//EXCLUDE-END-lockdiag- \r
+\r
+ // The number of waiters for locks\r
+ private final AtomicInteger blockCount;\r
+\r
+ /*\r
+ ** Constructor\r
+ */\r
+\r
+ ConcurrentLockSet(AbstractPool factory) {\r
+ this.factory = factory;\r
+ blockCount = new AtomicInteger();\r
+ locks = new ConcurrentHashMap<Lockable, Entry>();\r
+ }\r
+\r
+ /**\r
+ * Class representing an entry in the lock table.\r
+ */\r
+ private static final class Entry {\r
+ /** The lock control. */\r
+ Control control;\r
+ /**\r
+ * Mutex used to ensure single-threaded access to the LockControls. To\r
+ * avoid Java deadlocks, no thread should ever hold the mutex of more\r
+ * than one entry. Excepted from this requirement is a thread which\r
+ * performs deadlock detection. During deadlock detection, a thread\r
+ * might hold several mutexes, but it is not allowed to hold any mutex\r
+ * when entering the deadlock detection. Only one thread is allowed to\r
+ * perform deadlock detection at a time.\r
+ */\r
+ private final ReentrantLock mutex = new ReentrantLock();\r
+ /**\r
+ * Condition variable which prevents calls to <code>lock()</code> from\r
+ * locking the entry. If it is not <code>null</code>, only the thread\r
+ * performing deadlock detection may lock the entry (by calling\r
+ * <code>lockForDeadlockDetection()</code>).\r
+ */\r
+ private Condition deadlockDetection;\r
+\r
+ /**\r
+ * Lock the entry, ensuring exclusive access to the contained\r
+ * <code>Control</code> object. The call will block until the entry can\r
+ * be locked. If the entry is unlocked and\r
+ * <code>deadlockDetection</code> is not <code>null</code>, the entry\r
+ * belongs to a thread which waits for deadlock detection to be\r
+ * initiated, and the call will block until that thread has finished\r
+ * its deadlock detection.\r
+ */\r
+ void lock() {\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(!mutex.isHeldByCurrentThread());\r
+ }\r
+ mutex.lock();\r
+ while (deadlockDetection != null) {\r
+ deadlockDetection.awaitUninterruptibly();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Unlock the entry, allowing other threads to lock and access the\r
+ * contained <code>Control</code> object.\r
+ */\r
+ void unlock() {\r
+ mutex.unlock();\r
+ }\r
+\r
+ /**\r
+ * Lock the entry while performing deadlock detection. This method will\r
+ * lock the entry even when <code>deadlockDetection</code> is not\r
+ * <code>null</code>. If <code>deadlockDetection</code> is not\r
+ * <code>null</code>, we know the entry and its <code>Control</code>\r
+ * will not be accessed by others until we have finished the deadlock\r
+ * detection, so it's OK for us to access it.\r
+ *\r
+ */\r
+ void lockForDeadlockDetection() {\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(!mutex.isHeldByCurrentThread());\r
+ }\r
+ mutex.lock();\r
+ }\r
+\r
+ /**\r
+ * Notify that the lock request that is currently accessing the entry\r
+ * will be entering deadlock detection. Unlock the entry to allow the\r
+ * current thread or other threads to lock the entry for deadlock\r
+ * detection, but set the condition variable to prevent regular locking\r
+ * of the entry.\r
+ */\r
+ void enterDeadlockDetection() {\r
+ deadlockDetection = mutex.newCondition();\r
+ mutex.unlock();\r
+ }\r
+\r
+ /**\r
+ * Notify that the deadlock detection triggered by the current thread\r
+ * has finished. Re-lock the entry and notify any waiters that the\r
+ * deadlock detection has completed.\r
+ */\r
+ void exitDeadlockDetection() {\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(!mutex.isHeldByCurrentThread());\r
+ }\r
+ mutex.lock();\r
+ deadlockDetection.signalAll();\r
+ deadlockDetection = null;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get an entry from the lock table. If no entry exists for the\r
+ * <code>Lockable</code>, insert an entry. The returned entry will be\r
+ * locked and is guaranteed to still be present in the table.\r
+ *\r
+ * @param ref the <code>Lockable</code> whose entry to return\r
+ * @return the entry for the <code>Lockable</code>, locked for exclusive\r
+ * access\r
+ */\r
+ private Entry getEntry(Lockable ref) {\r
+ Entry e = locks.get(ref);\r
+ while (true) {\r
+ if (e != null) {\r
+ e.lock();\r
+ if (e.control != null) {\r
+ // entry is found and in use, return it\r
+ return e;\r
+ }\r
+ // entry is empty, hence it was removed from the table after we\r
+ // retrieved it. Try to reuse it later.\r
+ } else {\r
+ // no entry found, create a new one\r
+ e = new Entry();\r
+ e.lock();\r
+ }\r
+ // reinsert empty entry, or insert the new entry\r
+ Entry current = locks.putIfAbsent(ref, e);\r
+ if (current == null) {\r
+ // successfully (re-)inserted entry, return it\r
+ return e;\r
+ }\r
+ // someone beat us, unlock the old entry and retry with the entry\r
+ // they inserted\r
+ e.unlock();\r
+ e = current;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Check whether there is a deadlock. Make sure that only one thread enters\r
+ * deadlock detection at a time.\r
+ *\r
+ * @param entry the entry in the lock table for the lock request that\r
+ * triggered deadlock detection\r
+ * @param waitingLock the waiting lock\r
+ * @param wakeupReason the reason for waking up the waiter\r
+ * @return an object describing the deadlock\r
+ */\r
+ private Object[] checkDeadlock(Entry entry, ActiveLock waitingLock,\r
+ byte wakeupReason) {\r
+ LockControl control = (LockControl) entry.control;\r
+ // make sure that the entry is not blocking other threads performing\r
+ // deadlock detection since we have to wait for them to finish\r
+ entry.enterDeadlockDetection();\r
+ synchronized (Deadlock.class) {\r
+ try {\r
+ return Deadlock.look(factory, this, control, waitingLock,\r
+ wakeupReason);\r
+ } finally {\r
+ // unlock all entries we visited\r
+ for (Entry e : seenByDeadlockDetection) {\r
+ e.unlock();\r
+ }\r
+ seenByDeadlockDetection = null;\r
+ // re-lock the entry\r
+ entry.exitDeadlockDetection();\r
+ }\r
+ }\r
+ }\r
+\r
+ /*\r
+ ** Public Methods\r
+ */\r
+\r
+ /**\r
+ * Lock an object within a specific compatibility space.\r
+ *\r
+ * @param compatibilitySpace Compatibility space.\r
+ * @param ref Lockable reference.\r
+ * @param qualifier Qualifier.\r
+ * @param timeout Timeout in milli-seconds\r
+ *\r
+ * @return Object that represents the lock.\r
+ *\r
+ * @exception StandardException Standard Derby policy.\r
+\r
+ */\r
+ public Lock lockObject(CompatibilitySpace compatibilitySpace, Lockable ref,\r
+ Object qualifier, int timeout)\r
+ throws StandardException\r
+ { \r
+ if (SanityManager.DEBUG) {\r
+\r
+ if (SanityManager.DEBUG_ON("memoryLeakTrace")) {\r
+\r
+ if (locks.size() > 1000)\r
+ System.out.println("memoryLeakTrace:LockSet: " +\r
+ locks.size());\r
+ }\r
+ }\r
+\r
+ LockControl control;\r
+ Lock lockItem;\r
+ String lockDebug = null;\r
+\r
+ Entry entry = getEntry(ref);\r
+ try {\r
+\r
+ Control gc = entry.control;\r
+\r
+ if (gc == null) {\r
+\r
+ // object is not locked, can be granted\r
+ Lock gl = new Lock(compatibilitySpace, ref, qualifier);\r
+\r
+ gl.grant();\r
+\r
+ entry.control = gl;\r
+\r
+ return gl;\r
+ }\r
+\r
+ control = gc.getLockControl();\r
+ if (control != gc) {\r
+ entry.control = control;\r
+ }\r
+\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(ref.equals(control.getLockable()));\r
+ // ASSERT item is in the list\r
+ SanityManager.ASSERT(\r
+ locks.get(control.getLockable()).control == control);\r
+ }\r
+\r
+ lockItem = control.addLock(this, compatibilitySpace, qualifier);\r
+\r
+ if (lockItem.getCount() != 0) {\r
+ return lockItem;\r
+ }\r
+\r
+ if (timeout == C_LockFactory.NO_WAIT) {\r
+\r
+ // remove all trace of lock\r
+ control.giveUpWait(lockItem, this);\r
+\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ if (SanityManager.DEBUG_ON("DeadlockTrace"))\r
+ {\r
+\r
+ SanityManager.showTrace(new Throwable());\r
+\r
+ // The following dumps the lock table as it \r
+ // exists at the time a timeout is about to \r
+ // cause a deadlock exception to be thrown.\r
+\r
+ lockDebug = \r
+ DiagnosticUtil.toDiagString(lockItem) +\r
+ "\nCould not grant lock with zero timeout, " +\r
+ "here's the table";\r
+\r
+ // We cannot hold a lock on an entry while calling\r
+ // toDebugString() since it will lock other entries in\r
+ // the lock table. Holding the lock could cause a\r
+ // deadlock.\r
+ entry.unlock();\r
+ try {\r
+ lockDebug += toDebugString();\r
+ } finally {\r
+ // Re-lock the entry so that the outer finally\r
+ // clause doesn't fail.\r
+ entry.lock();\r
+ }\r
+ }\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ } finally {\r
+ entry.unlock();\r
+ }\r
+\r
+ boolean deadlockWait = false;\r
+ int actualTimeout;\r
+\r
+ if (timeout == C_LockFactory.WAIT_FOREVER)\r
+ {\r
+ // always check for deadlocks as there should not be any\r
+ deadlockWait = true;\r
+ if ((actualTimeout = deadlockTimeout) == C_LockFactory.WAIT_FOREVER)\r
+ actualTimeout = Property.DEADLOCK_TIMEOUT_DEFAULT * 1000;\r
+ }\r
+ else\r
+ {\r
+\r
+ if (timeout == C_LockFactory.TIMED_WAIT)\r
+ timeout = actualTimeout = waitTimeout;\r
+ else\r
+ actualTimeout = timeout;\r
+\r
+\r
+ // five posible cases\r
+ // i) timeout -1, deadlock -1 -> \r
+ // just wait forever, no deadlock check\r
+ // ii) timeout >= 0, deadlock -1 -> \r
+ // just wait for timeout, no deadlock check\r
+ // iii) timeout -1, deadlock >= 0 -> \r
+ // wait for deadlock, then deadlock check, \r
+ // then infinite timeout\r
+ // iv) timeout >=0, deadlock < timeout -> \r
+ // wait for deadlock, then deadlock check, \r
+ // then wait for (timeout - deadlock)\r
+ // v) timeout >=0, deadlock >= timeout -> \r
+ // just wait for timeout, no deadlock check\r
+\r
+\r
+ if (deadlockTimeout >= 0) {\r
+\r
+ if (actualTimeout < 0) {\r
+ // infinite wait but perform a deadlock check first\r
+ deadlockWait = true;\r
+ actualTimeout = deadlockTimeout;\r
+ } else if (deadlockTimeout < actualTimeout) {\r
+\r
+ // deadlock wait followed by a timeout wait\r
+\r
+ deadlockWait = true;\r
+ actualTimeout = deadlockTimeout;\r
+\r
+ // leave timeout as the remaining time\r
+ timeout -= deadlockTimeout;\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ ActiveLock waitingLock = (ActiveLock) lockItem;\r
+ lockItem = null;\r
+\r
+ int earlyWakeupCount = 0;\r
+ long startWaitTime = 0;\r
+\r
+forever: for (;;) {\r
+\r
+ byte wakeupReason = waitingLock.waitForGrant(actualTimeout);\r
+ \r
+ ActiveLock nextWaitingLock = null;\r
+ Object[] deadlockData = null;\r
+\r
+ try {\r
+ boolean willQuitWait;\r
+ Enumeration timeoutLockTable = null;\r
+ long currentTime = 0;\r
+ \r
+ entry.lock();\r
+ try {\r
+\r
+ if (control.isGrantable(\r
+ control.firstWaiter() == waitingLock,\r
+ compatibilitySpace,\r
+ qualifier)) {\r
+\r
+ // Yes, we are granted, put us on the granted queue.\r
+ control.grant(waitingLock);\r
+\r
+ // Remove from the waiting queue & get next waiter\r
+ nextWaitingLock = \r
+ control.getNextWaiter(waitingLock, true, this);\r
+\r
+ return waitingLock;\r
+ }\r
+\r
+ // try again later\r
+ waitingLock.clearPotentiallyGranted(); \r
+\r
+ willQuitWait = \r
+ (wakeupReason != Constants.WAITING_LOCK_GRANT);\r
+\r
+ if (((wakeupReason == Constants.WAITING_LOCK_IN_WAIT) &&\r
+ deadlockWait) ||\r
+ (wakeupReason == Constants.WAITING_LOCK_DEADLOCK))\r
+ {\r
+\r
+ // check for a deadlock, even if we were woken up \r
+ // because we were selected as a victim we still \r
+ // check because the situation may have changed.\r
+ deadlockData = \r
+ checkDeadlock(entry, waitingLock, wakeupReason);\r
+\r
+ if (deadlockData == null) {\r
+ // we don't have a deadlock\r
+ deadlockWait = false;\r
+\r
+ actualTimeout = timeout;\r
+ startWaitTime = 0;\r
+ willQuitWait = false;\r
+ } else {\r
+ willQuitWait = true;\r
+ }\r
+ }\r
+\r
+ nextWaitingLock = \r
+ control.getNextWaiter(\r
+ waitingLock, willQuitWait, this);\r
+\r
+\r
+ // If we were not woken by another then we have\r
+ // timed out. Either deadlock out or timeout\r
+ if (SanityManager.DEBUG &&\r
+ SanityManager.DEBUG_ON("DeadlockTrace") &&\r
+ willQuitWait) {\r
+ // Generate the first part of the debug message\r
+ // while holding the lock on entry, so that we have\r
+ // exclusive access to waitingLock. Wait until the\r
+ // entry has been unlocked before appending the\r
+ // contents of the lock table (to avoid deadlocks).\r
+ lockDebug =\r
+ DiagnosticUtil.toDiagString(waitingLock) +\r
+ "\nGot deadlock/timeout, here's the table";\r
+ }\r
+\r
+ } finally {\r
+ entry.unlock();\r
+ }\r
+\r
+ // need to do this outside of the synchronized block as the\r
+ // message text building (timeouts and deadlocks) may \r
+ // involve getting locks to look up table names from \r
+ // identifiers.\r
+\r
+ if (willQuitWait)\r
+ {\r
+ if (deadlockTrace && (deadlockData == null)) {\r
+ // if ending lock request due to lock timeout\r
+ // want a copy of the LockTable and the time,\r
+ // in case of deadlock deadlockData has the\r
+ // info we need.\r
+ currentTime = System.currentTimeMillis();\r
+ timeoutLockTable =\r
+ factory.makeVirtualLockTable();\r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON("DeadlockTrace")) {\r
+ SanityManager.showTrace(new Throwable());\r
+\r
+ // The following dumps the lock table as it\r
+ // exists at the time a timeout is about to\r
+ // cause a deadlock exception to be thrown.\r
+\r
+ lockDebug += toDebugString();\r
+ }\r
+\r
+ if (lockDebug != null)\r
+ {\r
+ String type = \r
+ ((deadlockData != null) ? \r
+ "deadlock:" : "timeout:"); \r
+\r
+ SanityManager.DEBUG_PRINT(\r
+ type,\r
+ "wait on lockitem caused " + type + \r
+ lockDebug);\r
+ }\r
+\r
+ }\r
+\r
+ if (deadlockData == null)\r
+ {\r
+ // ending wait because of lock timeout.\r
+\r
+ if (deadlockTrace)\r
+ { \r
+ // Turn ON derby.locks.deadlockTrace to build \r
+ // the lockTable.\r
+ \r
+ \r
+ throw Timeout.buildException(\r
+ waitingLock, timeoutLockTable, currentTime);\r
+ }\r
+ else\r
+ {\r
+ StandardException se = \r
+ StandardException.newException(\r
+ SQLState.LOCK_TIMEOUT);\r
+\r
+ throw se;\r
+ }\r
+ }\r
+ else \r
+ {\r
+ // ending wait because of lock deadlock.\r
+\r
+ throw Deadlock.buildException(\r
+ factory, deadlockData);\r
+ }\r
+ }\r
+ } finally {\r
+ if (nextWaitingLock != null) {\r
+ nextWaitingLock.wakeUp(Constants.WAITING_LOCK_GRANT);\r
+ nextWaitingLock = null;\r
+ }\r
+ }\r
+\r
+ if (actualTimeout != C_LockFactory.WAIT_FOREVER) {\r
+\r
+ if (wakeupReason != Constants.WAITING_LOCK_IN_WAIT)\r
+ earlyWakeupCount++;\r
+\r
+ if (earlyWakeupCount > 5) {\r
+\r
+ long now = System.currentTimeMillis();\r
+\r
+ if (startWaitTime != 0) {\r
+\r
+ long sleepTime = now - startWaitTime;\r
+\r
+ actualTimeout -= sleepTime;\r
+ }\r
+\r
+ startWaitTime = now;\r
+ }\r
+ }\r
+\r
+\r
+ } // for(;;)\r
+ }\r
+\r
+ /**\r
+ Unlock an object, previously locked by lockObject(). \r
+\r
+ If unlockCOunt is not zero then the lock will be unlocked\r
+ that many times, otherwise the unlock count is taken from\r
+ item.\r
+\r
+ */\r
+ public void unlock(Latch item, int unlockCount) {\r
+ // assume LockEntry is there\r
+ Entry entry = locks.get(item.getLockable());\r
+ entry.lock();\r
+ try {\r
+ unlock(entry, item, unlockCount);\r
+ } finally {\r
+ entry.unlock();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Unlock an object, previously locked by lockObject().\r
+ *\r
+ * @param entry the entry in which the lock is contained (the current\r
+ * thread must have locked the entry)\r
+ * @param item the item to unlock\r
+ * @param unlockCount the number of times to unlock the item (if zero, take\r
+ * the unlock count from item)\r
+ */\r
+ private void unlock(Entry entry, Latch item, int unlockCount) {\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(entry.mutex.isHeldByCurrentThread());\r
+ if (SanityManager.DEBUG_ON(Constants.LOCK_TRACE)) {\r
+ /*\r
+ ** I don't like checking the trace flag twice, but SanityManager\r
+ ** doesn't provide a way to get to the debug trace stream\r
+ ** directly.\r
+ */\r
+ SanityManager.DEBUG(\r
+ Constants.LOCK_TRACE, \r
+ "Release lock: " + DiagnosticUtil.toDiagString(item));\r
+ }\r
+ }\r
+\r
+ boolean tryGrant = false;\r
+ ActiveLock nextGrant = null;\r
+\r
+ Control control = entry.control;\r
+ \r
+ if (SanityManager.DEBUG) {\r
+\r
+ // only valid Lock's expected\r
+ if (item.getLockable() == null)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "item.getLockable() = null." +\r
+ "unlockCount " + unlockCount + \r
+ "item = " + DiagnosticUtil.toDiagString(item));\r
+ }\r
+\r
+ // only valid Lock's expected\r
+ if (control == null)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "control = null." +\r
+ "unlockCount " + unlockCount + \r
+ "item = " + DiagnosticUtil.toDiagString(item));\r
+ }\r
+\r
+ SanityManager.ASSERT(\r
+ locks.get(control.getLockable()).control == control);\r
+\r
+ if ((unlockCount != 0) && (unlockCount > item.getCount()))\r
+ SanityManager.THROWASSERT("unlockCount " + unlockCount +\r
+ " larger than actual lock count " + item.getCount() + " item " + item);\r
+ }\r
+\r
+ tryGrant = control.unlock(item, unlockCount);\r
+ item = null;\r
+\r
+ boolean mayBeEmpty = true;\r
+ if (tryGrant) {\r
+ nextGrant = control.firstWaiter();\r
+ if (nextGrant != null) {\r
+ mayBeEmpty = false;\r
+ if (!nextGrant.setPotentiallyGranted())\r
+ nextGrant = null;\r
+ }\r
+ }\r
+\r
+ if (mayBeEmpty) {\r
+ if (control.isEmpty()) {\r
+ // no-one granted, no-one waiting, remove lock control\r
+ locks.remove(control.getLockable());\r
+ entry.control = null;\r
+ }\r
+ return;\r
+ }\r
+\r
+ if (tryGrant && (nextGrant != null)) {\r
+ nextGrant.wakeUp(Constants.WAITING_LOCK_GRANT);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Unlock an object once if it is present in the specified group. Also\r
+ * remove the object from the group.\r
+ *\r
+ * @param space the compatibility space\r
+ * @param ref a reference to the locked object\r
+ * @param qualifier qualifier of the lock\r
+ * @param group a map representing the locks in a group\r
+ * @return the corresponding lock in the group map, or <code>null</code> if\r
+ * the object was not unlocked\r
+ */\r
+ public Lock unlockReference(CompatibilitySpace space, Lockable ref,\r
+ Object qualifier, Map group) {\r
+\r
+ Entry entry = locks.get(ref);\r
+ if (entry == null) {\r
+ return null;\r
+ }\r
+\r
+ entry.lock();\r
+ try {\r
+ Control control = entry.control;\r
+ if (control == null) {\r
+ return null;\r
+ }\r
+\r
+ Lock setLock = control.getLock(space, qualifier);\r
+ if (setLock == null) {\r
+ return null;\r
+ }\r
+\r
+ Lock lockInGroup = (Lock) group.remove(setLock);\r
+ if (lockInGroup != null) {\r
+ unlock(entry, lockInGroup, 1);\r
+ }\r
+\r
+ return lockInGroup;\r
+\r
+ } finally {\r
+ entry.unlock();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Lock an object and release the lock immediately. Equivalent to\r
+ * <pre>\r
+ * Lock lock = lockTable.lockObject(space, ref, qualifier, timeout);\r
+ * lockTable.unlock(lock, 1);\r
+ * </pre>\r
+ * except that the implementation is more efficient.\r
+ *\r
+ * @param space the compatibility space\r
+ * @param ref a reference to the locked object\r
+ * @param qualifier qualifier of the lock\r
+ * @param timeout maximum time to wait in milliseconds\r
+ * (<code>LockFactory.NO_WAIT</code> means don't wait)\r
+ * @return <code>true</code> if the object was locked, or\r
+ * <code>false</code>if the timeout was <code>NO_WAIT</code> and the lock\r
+ * couldn't be obtained immediately\r
+ * @exception StandardException if the lock could not be obtained\r
+ */\r
+ public boolean zeroDurationLockObject(\r
+ CompatibilitySpace space, Lockable ref, Object qualifier, int timeout)\r
+ throws StandardException {\r
+\r
+ if (SanityManager.DEBUG) {\r
+ if (SanityManager.DEBUG_ON(Constants.LOCK_TRACE)) {\r
+ D_LockControl.debugLock(\r
+ "Zero Duration Lock Request before Grant: ",\r
+ space, null, ref, qualifier, timeout);\r
+ if (SanityManager.DEBUG_ON(Constants.LOCK_STACK_TRACE)) {\r
+ // The following will print the stack trace of the lock\r
+ // request to the log.\r
+ Throwable t = new Throwable();\r
+ java.io.PrintWriter istream =\r
+ SanityManager.GET_DEBUG_STREAM();\r
+ istream.println("Stack trace of lock request:");\r
+ t.printStackTrace(istream);\r
+ }\r
+ }\r
+ }\r
+\r
+ // Very fast zeroDurationLockObject() for unlocked objects.\r
+ // If no entry exists in the lock manager for this reference\r
+ // then it must be unlocked.\r
+ // If the object is locked then we perform a grantable\r
+ // check, skipping over any waiters.\r
+ // If the caller wants to wait and the lock cannot\r
+ // be granted then we do the slow join the queue and\r
+ // release the lock method.\r
+\r
+ Entry entry = locks.get(ref);\r
+ if (entry == null) {\r
+ return true;\r
+ }\r
+\r
+ entry.lock();\r
+ try {\r
+ Control control = entry.control;\r
+ if (control == null) {\r
+ return true;\r
+ }\r
+\r
+ // If we are grantable, ignoring waiting locks then\r
+ // we can also grant this request now, as skipping\r
+ // over the waiters won't block them as we release\r
+ // the lock rightway.\r
+ if (control.isGrantable(true, space, qualifier)) {\r
+ return true;\r
+ }\r
+\r
+ // can't be granted and are not willing to wait.\r
+ if (timeout == C_LockFactory.NO_WAIT) {\r
+ return false;\r
+ }\r
+ } finally {\r
+ entry.unlock();\r
+ }\r
+\r
+ Lock lock = lockObject(space, ref, qualifier, timeout);\r
+\r
+ if (SanityManager.DEBUG) {\r
+ if (SanityManager.DEBUG_ON(Constants.LOCK_TRACE)) {\r
+ D_LockControl.debugLock(\r
+ "Zero Lock Request Granted: ",\r
+ space, null, ref, qualifier, timeout);\r
+ }\r
+ }\r
+\r
+ // and simply unlock it once\r
+ unlock(lock, 1);\r
+\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Set the deadlock timeout.\r
+ *\r
+ * @param timeout deadlock timeout in milliseconds\r
+ */\r
+ public void setDeadlockTimeout(int timeout) {\r
+ deadlockTimeout = timeout;\r
+ }\r
+\r
+ /**\r
+ * Set the wait timeout.\r
+ *\r
+ * @param timeout wait timeout in milliseconds\r
+ */\r
+ public void setWaitTimeout(int timeout) {\r
+ waitTimeout = timeout;\r
+ }\r
+ \r
+ /*\r
+ ** Non public methods\r
+ */\r
+//EXCLUDE-START-lockdiag- \r
+\r
+ public void setDeadlockTrace(boolean val)\r
+ {\r
+ // set this without synchronization\r
+ deadlockTrace = val;\r
+ } \r
+//EXCLUDE-END-lockdiag- \r
+\r
+ private String toDebugString()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ String str = new String();\r
+\r
+ int i = 0;\r
+ for (Entry entry : locks.values())\r
+ {\r
+ entry.lock();\r
+ try {\r
+ str += "\n lock[" + i + "]: " +\r
+ DiagnosticUtil.toDiagString(entry.control);\r
+ } finally {\r
+ entry.unlock();\r
+ }\r
+ }\r
+\r
+ return(str);\r
+ }\r
+ else\r
+ {\r
+ return(null);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Add all waiters in this lock table to a <code>Map</code> object.\r
+ * This method can only be called by the thread that is currently\r
+ * performing deadlock detection. All entries that are visited in the lock\r
+ * table will be locked when this method returns. The entries that have\r
+ * been seen and locked will be unlocked after the deadlock detection has\r
+ * finished.\r
+ */\r
+ public void addWaiters(Map waiters) {\r
+ seenByDeadlockDetection = new ArrayList<Entry>(locks.size());\r
+ for (Entry entry : locks.values()) {\r
+ seenByDeadlockDetection.add(entry);\r
+ entry.lockForDeadlockDetection();\r
+ if (entry.control != null) {\r
+ entry.control.addWaiters(waiters);\r
+ }\r
+ }\r
+ }\r
+\r
+//EXCLUDE-START-lockdiag- \r
+ /**\r
+ * make a shallow clone of myself and my lock controls\r
+ */\r
+ public Map<Lockable, Control> shallowClone() {\r
+ HashMap<Lockable, Control> clone = new HashMap<Lockable, Control>();\r
+\r
+ for (Entry entry : locks.values()) {\r
+ entry.lock();\r
+ try {\r
+ Control control = entry.control;\r
+ if (control != null) {\r
+ clone.put(control.getLockable(), control.shallowClone());\r
+ }\r
+ } finally {\r
+ entry.unlock();\r
+ }\r
+ }\r
+\r
+ return clone;\r
+ }\r
+//EXCLUDE-END-lockdiag- \r
+\r
+ /**\r
+ * Increase blockCount by one.\r
+ */\r
+ public void oneMoreWaiter() {\r
+ blockCount.incrementAndGet();\r
+ }\r
+\r
+ /**\r
+ * Decrease blockCount by one.\r
+ */\r
+ public void oneLessWaiter() {\r
+ blockCount.decrementAndGet();\r
+ }\r
+\r
+ /**\r
+ * Check whether anyone is blocked.\r
+ * @return <code>true</code> if someone is blocked, <code>false</code>\r
+ * otherwise\r
+ */\r
+ public boolean anyoneBlocked() {\r
+ int blocked = blockCount.get();\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(\r
+ blocked >= 0, "blockCount should not be negative");\r
+ }\r
+ return blocked != 0;\r
+ }\r
+}\r