--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.services.locks.LockSpace\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.Lockable;\r
+import org.apache.derby.iapi.services.locks.Limit;\r
+\r
+import org.apache.derby.iapi.util.Matchable;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import java.util.Enumeration;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+\r
+/**\r
+\r
+ A LockSpace represents the complete set of locks held within\r
+ a single compatibility space, broken into groups of locks.\r
+\r
+ A LockSpace contains a HashMap keyed by the group reference,\r
+ the data for each key is a HashMap of Lock's.\r
+\r
+ <p> A <code>LockSpace</code> can have an owner (for instance a\r
+ transaction). Currently, the owner is used by the virtual lock table to\r
+ find out which transaction a lock belongs to. Some parts of the code also\r
+ use the owner as a group object which guarantees that the lock is released\r
+ on a commit or an abort. The owner has no special meaning to the lock\r
+ manager and can be any object, including <code>null</code>. </p>\r
+*/\r
+final class LockSpace implements CompatibilitySpace {\r
+\r
+ /** Map from group references to groups of locks. */\r
+ private final HashMap groups;\r
+ /** Reference to the owner of this compatibility space. */\r
+ private final Object owner;\r
+\r
+ private HashMap spareGroups[] = new HashMap[3];\r
+\r
+ // the Limit info.\r
+ private Object callbackGroup;\r
+ private int limit;\r
+ private int nextLimitCall;\r
+ private Limit callback;\r
+\r
+ /**\r
+ * Creates a new <code>LockSpace</code> instance.\r
+ *\r
+ * @param owner an object representing the owner of the compatibility space\r
+ */\r
+ LockSpace(Object owner) {\r
+ groups = new HashMap();\r
+ this.owner = owner;\r
+ }\r
+\r
+ /**\r
+ * Get the object representing the owner of the compatibility space.\r
+ *\r
+ * @return the owner of the compatibility space\r
+ */\r
+ public Object getOwner() {\r
+ return owner;\r
+ }\r
+\r
+ /**\r
+ Add a lock to a group.\r
+ */\r
+ protected synchronized void addLock(Object group, Lock lock)\r
+ throws StandardException {\r
+\r
+ Lock lockInGroup = null;\r
+\r
+ HashMap dl = (HashMap) groups.get(group);\r
+ if (dl == null) {\r
+ dl = getGroupMap(group);\r
+ } else if (lock.getCount() != 1) {\r
+ lockInGroup = (Lock) dl.get(lock);\r
+ }\r
+\r
+ if (lockInGroup == null) {\r
+ lockInGroup = lock.copy();\r
+ dl.put(lockInGroup, lockInGroup);\r
+ }\r
+ lockInGroup.count++;\r
+\r
+ if (inLimit)\r
+ return;\r
+\r
+ if (!group.equals(callbackGroup))\r
+ return;\r
+\r
+ int groupSize = dl.size();\r
+ \r
+ if (groupSize > nextLimitCall) {\r
+\r
+ inLimit = true;\r
+ callback.reached(this, group, limit,\r
+ new LockList(java.util.Collections.enumeration(dl.keySet())), groupSize);\r
+ inLimit = false;\r
+\r
+ // see when the next callback should occur, if the callback\r
+ // failed to release a sufficent amount of locks then\r
+ // delay until another "limit" locks are obtained.\r
+ int newGroupSize = dl.size();\r
+ if (newGroupSize < (limit / 2))\r
+ nextLimitCall = limit;\r
+ else if (newGroupSize < (nextLimitCall / 2))\r
+ nextLimitCall -= limit;\r
+ else\r
+ nextLimitCall += limit;\r
+\r
+ }\r
+ }\r
+ \r
+ private boolean inLimit;\r
+ /**\r
+ Unlock all the locks in a group and then remove the group.\r
+ */\r
+\r
+ synchronized void unlockGroup(LockTable lset, Object group) {\r
+ HashMap dl = (HashMap) groups.remove(group);\r
+ if (dl == null)\r
+ return;\r
+\r
+ for (Iterator list = dl.keySet().iterator(); list.hasNext(); ) {\r
+ lset.unlock((Lock) list.next(), 0);\r
+ }\r
+\r
+ if ((callbackGroup != null) && group.equals(callbackGroup)) {\r
+ nextLimitCall = limit;\r
+ }\r
+\r
+ saveGroup(dl);\r
+ }\r
+\r
+ private HashMap getGroupMap(Object group) {\r
+ HashMap[] sg = spareGroups;\r
+ HashMap dl = null;\r
+ for (int i = 0; i < 3; i++) {\r
+ dl = sg[i];\r
+ if (dl != null) {\r
+ sg[i] = null;\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (dl == null)\r
+ dl = new HashMap(5, 0.8f);\r
+\r
+ groups.put(group, dl);\r
+ return dl;\r
+ }\r
+ private void saveGroup(HashMap dl) {\r
+ HashMap[] sg = spareGroups;\r
+ for (int i = 0; i < 3; i++) {\r
+ if (sg[i] == null) {\r
+ sg[i] = dl;\r
+ dl.clear();\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ Unlock all locks in the group that match the key\r
+ */\r
+ synchronized void unlockGroup(LockTable lset, Object group, Matchable key) {\r
+ HashMap dl = (HashMap) groups.get(group);\r
+ if (dl == null)\r
+ return; // no group at all\r
+\r
+ boolean allUnlocked = true;\r
+ for (Iterator e = dl.keySet().iterator(); e.hasNext(); ) {\r
+\r
+ Lock lock = (Lock) e.next();\r
+ if (!key.match(lock.getLockable())) {\r
+ allUnlocked = false;\r
+ continue;\r
+ }\r
+ lset.unlock(lock, 0);\r
+ e.remove();\r
+ }\r
+\r
+ if (allUnlocked) {\r
+ groups.remove(group);\r
+ saveGroup(dl);\r
+ if ((callbackGroup != null) && group.equals(callbackGroup)) {\r
+ nextLimitCall = limit;\r
+ }\r
+ }\r
+ }\r
+\r
+ synchronized void transfer(Object oldGroup, Object newGroup) {\r
+ HashMap from = (HashMap) groups.get(oldGroup);\r
+ if (from == null)\r
+ return;\r
+\r
+ HashMap to = (HashMap) groups.get(newGroup);\r
+ if (to == null) {\r
+ // simple case \r
+ groups.put(newGroup, from);\r
+ clearLimit(oldGroup);\r
+ groups.remove(oldGroup);\r
+ return;\r
+ }\r
+\r
+ if (to.size() < from.size()) {\r
+\r
+ // place the contents of to into from\r
+ mergeGroups(to, from);\r
+\r
+ Object oldTo = groups.put(newGroup, from);\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(oldTo == to, "inconsistent state in LockSpace");\r
+ }\r
+\r
+ } else {\r
+ mergeGroups(from, to);\r
+ }\r
+ \r
+ clearLimit(oldGroup);\r
+ groups.remove(oldGroup);\r
+ }\r
+\r
+ private void mergeGroups(HashMap from, HashMap into) {\r
+\r
+ for (Iterator e = from.keySet().iterator(); e.hasNext(); ) {\r
+\r
+ Object lock = e.next();\r
+\r
+ Object lockI = into.get(lock);\r
+\r
+ if (lockI == null) {\r
+ // lock is only in from list\r
+ into.put(lock, lock);\r
+ } else {\r
+ // merge the locks\r
+ Lock fromL = (Lock) lock;\r
+ Lock intoL = (Lock) lockI;\r
+\r
+ intoL.count += fromL.getCount();\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ synchronized int unlockReference(LockTable lset, Lockable ref,\r
+ Object qualifier, Object group) {\r
+\r
+ // look for locks matching our reference and qualifier.\r
+ HashMap dl = (HashMap) groups.get(group);\r
+ if (dl == null)\r
+ return 0;\r
+\r
+ Lock lockInGroup = lset.unlockReference(this, ref, qualifier, dl);\r
+ if (lockInGroup == null) {\r
+ return 0;\r
+ }\r
+\r
+ if (lockInGroup.getCount() == 1) {\r
+\r
+ if (dl.isEmpty()) {\r
+ groups.remove(group);\r
+ saveGroup(dl);\r
+ if ((callbackGroup != null) && group.equals(callbackGroup)) {\r
+ nextLimitCall = limit;\r
+ }\r
+ }\r
+\r
+ return 1;\r
+ }\r
+ \r
+ // the lock item will be left in the group\r
+ lockInGroup.count--;\r
+ dl.put(lockInGroup, lockInGroup);\r
+ return 1;\r
+ }\r
+\r
+ /**\r
+ Return true if locks are held in a group\r
+ */\r
+ synchronized boolean areLocksHeld(Object group) {\r
+ return groups.containsKey(group);\r
+ }\r
+\r
+ /**\r
+ * Return true if locks are held in this compatibility space.\r
+ * @return true if locks are held, false otherwise\r
+ */\r
+ synchronized boolean areLocksHeld() {\r
+ return !groups.isEmpty();\r
+ }\r
+ \r
+ synchronized boolean isLockHeld(Object group, Lockable ref, Object qualifier) {\r
+\r
+ // look for locks matching our reference and qualifier.\r
+ HashMap dl = (HashMap) groups.get(group);\r
+ if (dl == null)\r
+ return false;\r
+\r
+ Object heldLock = dl.get(new Lock(this, ref, qualifier));\r
+ return (heldLock != null);\r
+ }\r
+\r
+ synchronized void setLimit(Object group, int limit, Limit callback) {\r
+ callbackGroup = group;\r
+ this.nextLimitCall = this.limit = limit;\r
+ this.callback = callback;\r
+ }\r
+\r
+ /**\r
+ Clear a limit set by setLimit.\r
+ */\r
+ synchronized void clearLimit(Object group) {\r
+ if (group.equals(callbackGroup)) {\r
+ callbackGroup = null;\r
+ nextLimitCall = limit = Integer.MAX_VALUE;\r
+ callback = null;\r
+ }\r
+ }\r
+\r
+ /**\r
+ Return a count of the number of locks\r
+ held by this space. The argument bail\r
+ indicates at which point the counting\r
+ should bail out and return the current\r
+ count. This routine will bail if the\r
+ count is greater than bail. Thus this\r
+ routine is intended to for deadlock\r
+ code to find the space with the\r
+ fewest number of locks.\r
+ */\r
+ synchronized int deadlockCount(int bail) {\r
+\r
+ int count = 0;\r
+\r
+ for (Iterator it = groups.values().iterator(); it.hasNext(); ) {\r
+ HashMap group = (HashMap) it.next();\r
+ for (Iterator locks = group.keySet().iterator(); locks.hasNext(); ) {\r
+ Lock lock = (Lock) locks.next();\r
+ count += lock.getCount();\r
+ if (count > bail)\r
+ return count;\r
+ }\r
+ }\r
+ return count;\r
+\r
+ }\r
+}\r
+\r
+/**\r
+ An Enumeration that returns the the Lockables\r
+ in a group.\r
+*/\r
+\r
+class LockList implements Enumeration {\r
+\r
+ private Enumeration lockGroup;\r
+\r
+ LockList(Enumeration lockGroup) {\r
+ this.lockGroup = lockGroup;\r
+ }\r
+\r
+ public boolean hasMoreElements() {\r
+ return lockGroup.hasMoreElements();\r
+ }\r
+\r
+ public Object nextElement() {\r
+ return ((Lock) lockGroup.nextElement()).getLockable();\r
+ }\r
+}\r