package com.sleepycat.je.txn; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.dbi.MemoryBudget; import de.ovgu.cide.jakutil.*; /** * A Lock embodies the lock state of a NodeId. It includes a set of owners and * a list of waiters. */ public class Lock { /** * A single locker always appears only once in the logical set of owners. * The owners set is always in one of the following states. * 1- Empty * 2- A single writer * 3- One or more readers * 4- Multiple writers or a mix of readers and writers, all for * txns which share locks (all ThreadLocker instances for the same * thread) * Both ownerSet and waiterList are a collection of LockInfo. Since the * common case is that there is only one owner or waiter, we have added an * optimization to avoid the cost of collections. FirstOwner and * firstWaiter are used for the first owner or waiter of the lock, and the * corresponding collection is instantiated and used only if more owners * arrive. * In terms of memory accounting, we count up the cost of each added or * removed LockInfo, but not the cost of the HashSet/List entry * overhead. We could do the latter for more precise accounting. */ private LockInfo firstOwner; private Set ownerSet; private LockInfo firstWaiter; private List waiterList; private Long nodeId; /** * Create a Lock. */ Lock( Long nodeId){ this.nodeId=nodeId; } public Lock(){ } Long getNodeId(){ return nodeId; } /** * The first waiter goes into the firstWaiter member variable. Once the * waiterList is made, all appended waiters go into waiterList, even after * the firstWaiter goes away and leaves that field null, so as to leave the * list ordered. */ private void addWaiterToEndOfList( LockInfo waiter, MemoryBudget mb, int lockTableIndex){ if (waiterList == null) { if (firstWaiter == null) { firstWaiter=waiter; } else { waiterList=new ArrayList(); waiterList.add(waiter); } } else { waiterList.add(waiter); } this.hook760(mb,lockTableIndex); } /** * Add this waiter to the front of the list. */ private void addWaiterToHeadOfList( LockInfo waiter, MemoryBudget mb, int lockTableIndex){ if (firstWaiter != null) { if (waiterList == null) { waiterList=new ArrayList(); } waiterList.add(0,firstWaiter); } firstWaiter=waiter; this.hook761(mb,lockTableIndex); } /** * Get a list of waiters for debugging and error messages. */ List getWaitersListClone(){ List dumpWaiters=new ArrayList(); if (firstWaiter != null) { dumpWaiters.add(firstWaiter); } if (waiterList != null) { dumpWaiters.addAll(waiterList); } return dumpWaiters; } /** * Remove this locker from the waiter list. */ void flushWaiter( Locker locker, MemoryBudget mb, int lockTableIndex){ if ((firstWaiter != null) && (firstWaiter.getLocker() == locker)) { firstWaiter=null; this.hook762(mb,lockTableIndex); } else if (waiterList != null) { Iterator iter=waiterList.iterator(); while (iter.hasNext()) { LockInfo info=(LockInfo)iter.next(); if (info.getLocker() == locker) { iter.remove(); this.hook763(mb,lockTableIndex); return; } } } } private void addOwner( LockInfo newLock, MemoryBudget mb, int lockTableIndex){ if (firstOwner == null) { firstOwner=newLock; } else { if (ownerSet == null) { ownerSet=new HashSet(); } ownerSet.add(newLock); } this.hook764(mb,lockTableIndex); } /** * Get a new Set of the owners. */ Set getOwnersClone(){ Set owners; if (ownerSet != null) { owners=new HashSet(ownerSet); } else { owners=new HashSet(); } if (firstOwner != null) { owners.add(firstOwner); } return owners; } /** * Remove this LockInfo from the owner set. */ private boolean flushOwner( LockInfo oldOwner, MemoryBudget mb, int lockTableIndex){ boolean removed=false; if (oldOwner != null) { if (firstOwner == oldOwner) { firstOwner=null; return true; } if (ownerSet != null) { removed=ownerSet.remove(oldOwner); } } this.hook765(mb,lockTableIndex,removed); return removed; } /** * Remove this locker from the owner set. */ private LockInfo flushOwner( Locker locker, MemoryBudget mb, int lockTableIndex){ LockInfo flushedInfo=null; if ((firstOwner != null) && (firstOwner.getLocker() == locker)) { flushedInfo=firstOwner; firstOwner=null; } else if (ownerSet != null) { Iterator iter=ownerSet.iterator(); while (iter.hasNext()) { LockInfo o=(LockInfo)iter.next(); if (o.getLocker() == locker) { iter.remove(); flushedInfo=o; } } } this.hook766(mb,lockTableIndex,flushedInfo); return flushedInfo; } /** * Returns the owner LockInfo for a locker, or null if locker is not an * owner. */ private LockInfo getOwnerLockInfo( Locker locker){ if ((firstOwner != null) && (firstOwner.getLocker() == locker)) { return firstOwner; } if (ownerSet != null) { Iterator iter=ownerSet.iterator(); while (iter.hasNext()) { LockInfo o=(LockInfo)iter.next(); if (o.getLocker() == locker) { return o; } } } return null; } /** * Return true if locker is an owner of this Lock for lockType, * false otherwise. * This method is only used by unit tests. */ boolean isOwner( Locker locker, LockType lockType){ LockInfo o=getOwnerLockInfo(locker); if (o != null) { LockType ownedLockType=o.getLockType(); if (lockType == ownedLockType) { return true; } LockUpgrade upgrade=ownedLockType.getUpgrade(lockType); if (!upgrade.getPromotion()) { return true; } } return false; } /** * Return true if locker is an owner of this Lock and this is a write * lock. */ boolean isOwnedWriteLock( Locker locker){ LockInfo o=getOwnerLockInfo(locker); return o != null && o.getLockType().isWriteLock(); } /** * Return true if locker is a waiter on this Lock. * This method is only used by unit tests. */ boolean isWaiter( Locker locker){ if (firstWaiter != null) { if (firstWaiter.getLocker() == locker) { return true; } } if (waiterList != null) { Iterator iter=waiterList.iterator(); while (iter.hasNext()) { LockInfo info=(LockInfo)iter.next(); if (info.getLocker() == locker) { return true; } } } return false; } int nWaiters(){ int count=0; if (firstWaiter != null) { count++; } if (waiterList != null) { count+=waiterList.size(); } return count; } int nOwners(){ int count=0; if (firstOwner != null) { count++; } if (ownerSet != null) { count+=ownerSet.size(); } return count; } /** * Attempts to acquire the lock and returns the LockGrantType. * Assumes we hold the lockTableLatch when entering this method. */ LockGrantType lock( LockType requestType, Locker locker, boolean nonBlockingRequest, MemoryBudget mb, int lockTableIndex){ assert validateRequest(locker); LockInfo newLock=new LockInfo(locker,requestType); LockGrantType grant=tryLock(newLock,nWaiters() == 0,mb,lockTableIndex); if (grant == LockGrantType.WAIT_NEW || grant == LockGrantType.WAIT_PROMOTION || grant == LockGrantType.WAIT_RESTART) { if (requestType.getCausesRestart() && grant != LockGrantType.WAIT_RESTART) { LockInfo waiter=null; Iterator iter=null; if (waiterList != null) { iter=waiterList.iterator(); } if (firstWaiter != null) { waiter=firstWaiter; } else if ((iter != null) && (iter.hasNext())) { waiter=(LockInfo)iter.next(); } while (waiter != null) { Locker waiterLocker=waiter.getLocker(); LockType waiterType=waiter.getLockType(); if (waiterType != LockType.RESTART && locker != waiterLocker && !locker.sharesLocksWith(waiterLocker)) { LockConflict conflict=waiterType.getConflict(requestType); if (conflict.getRestart()) { grant=LockGrantType.WAIT_RESTART; break; } } if ((iter != null) && (iter.hasNext())) { waiter=(LockInfo)iter.next(); } else { waiter=null; } } } if (nonBlockingRequest) { grant=LockGrantType.DENIED; } else { if (grant == LockGrantType.WAIT_PROMOTION) { addWaiterToHeadOfList(newLock,mb,lockTableIndex); } else { assert grant == LockGrantType.WAIT_NEW || grant == LockGrantType.WAIT_RESTART; if (grant == LockGrantType.WAIT_RESTART) { newLock.setLockType(LockType.RESTART); } addWaiterToEndOfList(newLock,mb,lockTableIndex); } } } return grant; } /** * Releases a lock and moves the next waiter(s) to the owners. * @returnnull if we were not the owner, * a non-empty set if owners should be notified after releasing, * an empty set if no notification is required. */ Set release( Locker locker, MemoryBudget mb, int lockTableIndex){ LockInfo removedLock=flushOwner(locker,mb,lockTableIndex); if (removedLock == null) { return null; } Set lockersToNotify=Collections.EMPTY_SET; if (nWaiters() == 0) { return lockersToNotify; } LockInfo waiter=null; Iterator iter=null; boolean isFirstWaiter=false; if (waiterList != null) { iter=waiterList.iterator(); } if (firstWaiter != null) { waiter=firstWaiter; isFirstWaiter=true; } else if ((iter != null) && (iter.hasNext())) { waiter=(LockInfo)iter.next(); } while (waiter != null) { LockType waiterType=waiter.getLockType(); Locker waiterLocker=waiter.getLocker(); LockGrantType grant; if (waiterType == LockType.RESTART) { grant=rangeInsertConflict(waiterLocker) ? LockGrantType.WAIT_NEW : LockGrantType.NEW; } else { grant=tryLock(waiter,true,mb,lockTableIndex); } if (grant == LockGrantType.NEW || grant == LockGrantType.EXISTING || grant == LockGrantType.PROMOTION) { if (isFirstWaiter) { firstWaiter=null; } else { iter.remove(); } if (lockersToNotify == Collections.EMPTY_SET) { lockersToNotify=new HashSet(); } lockersToNotify.add(waiterLocker); this.hook767(mb,lockTableIndex); } else { assert grant == LockGrantType.WAIT_NEW || grant == LockGrantType.WAIT_PROMOTION || grant == LockGrantType.WAIT_RESTART; break; } if ((iter != null) && (iter.hasNext())) { waiter=(LockInfo)iter.next(); isFirstWaiter=false; } else { waiter=null; } } return lockersToNotify; } /** * Called from lock() to try locking a new request, and from release() to * try locking a waiting request. * @param newLock is the lock that is requested. * @param firstWaiterInLine determines whether to grant the lock when a * NEW lock can be granted, but other non-conflicting owners exist; for * example, when a new READ lock is requested but READ locks are held by * other owners. This parameter should be true if the requestor is the * first waiter in line (or if there are no waiters), and false otherwise. * @param mb is the current memory budget. * @return LockGrantType.EXISTING, NEW, PROMOTION, WAIT_RESTART, WAIT_NEW * or WAIT_PROMOTION. */ private LockGrantType tryLock( LockInfo newLock, boolean firstWaiterInLine, MemoryBudget mb, int lockTableIndex){ if (nOwners() == 0) { addOwner(newLock,mb,lockTableIndex); return LockGrantType.NEW; } Locker locker=newLock.getLocker(); LockType requestType=newLock.getLockType(); LockUpgrade upgrade=null; LockInfo lockToUpgrade=null; boolean ownerExists=false; boolean ownerConflicts=false; LockInfo owner=null; Iterator iter=null; if (ownerSet != null) { iter=ownerSet.iterator(); } if (firstOwner != null) { owner=firstOwner; } else if ((iter != null) && (iter.hasNext())) { owner=(LockInfo)iter.next(); } while (owner != null) { Locker ownerLocker=owner.getLocker(); LockType ownerType=owner.getLockType(); if (locker == ownerLocker) { assert (upgrade == null); upgrade=ownerType.getUpgrade(requestType); if (upgrade.getUpgrade() == null) { return LockGrantType.EXISTING; } else { lockToUpgrade=owner; } } else { if (!locker.sharesLocksWith(ownerLocker)) { LockConflict conflict=ownerType.getConflict(requestType); if (conflict.getRestart()) { return LockGrantType.WAIT_RESTART; } else { if (!conflict.getAllowed()) { ownerConflicts=true; } ownerExists=true; } } } if ((iter != null) && (iter.hasNext())) { owner=(LockInfo)iter.next(); } else { owner=null; } } if (upgrade != null) { LockType upgradeType=upgrade.getUpgrade(); assert upgradeType != null; if (!ownerConflicts) { lockToUpgrade.setLockType(upgradeType); return upgrade.getPromotion() ? LockGrantType.PROMOTION : LockGrantType.EXISTING; } else { return LockGrantType.WAIT_PROMOTION; } } else { if (!ownerConflicts && (!ownerExists || firstWaiterInLine)) { addOwner(newLock,mb,lockTableIndex); return LockGrantType.NEW; } else { return LockGrantType.WAIT_NEW; } } } /** * Called from release() when a RESTART request is waiting to determine if * any RANGE_INSERT owners exist. We can't call tryLock for a RESTART * lock because it must never be granted. */ private boolean rangeInsertConflict( Locker waiterLocker){ LockInfo owner=null; Iterator iter=null; if (ownerSet != null) { iter=ownerSet.iterator(); } if (firstOwner != null) { owner=firstOwner; } else if ((iter != null) && (iter.hasNext())) { owner=(LockInfo)iter.next(); } while (owner != null) { Locker ownerLocker=owner.getLocker(); if (ownerLocker != waiterLocker && !ownerLocker.sharesLocksWith(waiterLocker) && owner.getLockType() == LockType.RANGE_INSERT) { return true; } if ((iter != null) && (iter.hasNext())) { owner=(LockInfo)iter.next(); } else { owner=null; } } return false; } /** * Downgrade a write lock to a read lock. */ void demote( Locker locker){ LockInfo owner=getOwnerLockInfo(locker); if (owner != null) { LockType type=owner.getLockType(); if (type.isWriteLock()) { owner.setLockType((type == LockType.RANGE_WRITE) ? LockType.RANGE_READ : LockType.READ); } } } /** * Transfer a lock from one transaction to another. Make sure that this * destination locker is only present as a single reader or writer. */ LockType transfer( Locker currentLocker, Locker destLocker, MemoryBudget mb, int lockTableIndex) throws DatabaseException { LockType lockType=null; int numRemovedLockInfos=0; if (firstOwner != null) { if (firstOwner.getLocker() == destLocker) { firstOwner=null; numRemovedLockInfos++; } else if (firstOwner.getLocker() == currentLocker) { lockType=setNewLocker(firstOwner,destLocker); } } if (ownerSet != null) { Iterator iter=ownerSet.iterator(); while (iter.hasNext()) { LockInfo owner=(LockInfo)iter.next(); if (owner.getLocker() == destLocker) { iter.remove(); numRemovedLockInfos++; } else if (owner.getLocker() == currentLocker) { lockType=setNewLocker(owner,destLocker); } } } if ((firstWaiter != null) && (firstWaiter.getLocker() == destLocker)) { firstWaiter=null; numRemovedLockInfos++; } if (waiterList != null) { Iterator iter=waiterList.iterator(); while (iter.hasNext()) { LockInfo info=(LockInfo)iter.next(); if (info.getLocker() == destLocker) { iter.remove(); numRemovedLockInfos++; } } } this.hook768(mb,lockTableIndex,numRemovedLockInfos); return lockType; } private LockType setNewLocker( LockInfo owner, Locker destLocker) throws DatabaseException { owner.setLocker(destLocker); destLocker.addLock(nodeId,this,owner.getLockType(),LockGrantType.NEW); return owner.getLockType(); } /** * Transfer a lock from one transaction to many others. Only really needed * for case where a write handle lock is being transferred to multiple read * handles. */ LockType transferMultiple( Locker currentLocker, Locker[] destLockers, MemoryBudget mb, int lockTableIndex) throws DatabaseException { LockType lockType=null; LockInfo oldOwner=null; if (destLockers.length == 1) { return transfer(currentLocker,destLockers[0],mb,lockTableIndex); } else { if (firstOwner != null) { for (int i=0; i < destLockers.length; i++) { if (firstOwner.getLocker() == destLockers[i]) { firstOwner=null; break; } } } if (ownerSet != null) { Iterator ownersIter=ownerSet.iterator(); while (ownersIter.hasNext()) { LockInfo o=(LockInfo)ownersIter.next(); for (int i=0; i < destLockers.length; i++) { if (o.getLocker() == destLockers[i]) { ownersIter.remove(); break; } } } } if (firstOwner != null) { oldOwner=cloneLockInfo(firstOwner,currentLocker,destLockers,mb,lockTableIndex); } if ((ownerSet != null) && (oldOwner == null)) { Iterator ownersIter=ownerSet.iterator(); while (ownersIter.hasNext()) { LockInfo o=(LockInfo)ownersIter.next(); oldOwner=cloneLockInfo(o,currentLocker,destLockers,mb,lockTableIndex); if (oldOwner != null) { break; } } } if (firstWaiter != null) { for (int i=0; i < destLockers.length; i++) { if (firstWaiter.getLocker() == destLockers[i]) { firstWaiter=null; break; } } } if (waiterList != null) { Iterator iter=waiterList.iterator(); while (iter.hasNext()) { LockInfo o=(LockInfo)iter.next(); for (int i=0; i < destLockers.length; i++) { if (o.getLocker() == destLockers[i]) { iter.remove(); break; } } } } } boolean removed=flushOwner(oldOwner,mb,lockTableIndex); assert removed; return lockType; } /** * If oldOwner is the current owner, clone it and transform it into a dest * locker. */ private LockInfo cloneLockInfo( LockInfo oldOwner, Locker currentLocker, Locker[] destLockers, MemoryBudget mb, int lockTableIndex) throws DatabaseException { if (oldOwner.getLocker() == currentLocker) { try { LockType lockType=oldOwner.getLockType(); for (int i=0; i < destLockers.length; i++) { LockInfo clonedLockInfo=(LockInfo)oldOwner.clone(); clonedLockInfo.setLocker(destLockers[i]); destLockers[i].addLock(nodeId,this,lockType,LockGrantType.NEW); addOwner(clonedLockInfo,mb,lockTableIndex); } return oldOwner; } catch ( CloneNotSupportedException e) { throw new DatabaseException(e); } } else { return null; } } /** * Return the locker that has a write ownership on this lock. If no * write owner exists, return null. */ Locker getWriteOwnerLocker(){ LockInfo owner=null; Iterator iter=null; if (ownerSet != null) { iter=ownerSet.iterator(); } if (firstOwner != null) { owner=firstOwner; } else if ((iter != null) && (iter.hasNext())) { owner=(LockInfo)iter.next(); } while (owner != null) { if (owner.getLockType().isWriteLock()) { return owner.getLocker(); } if ((iter != null) && (iter.hasNext())) { owner=(LockInfo)iter.next(); } else { owner=null; } } return null; } /** * Debugging aid, validation before a lock request. */ private boolean validateRequest( Locker locker){ if (firstWaiter != null) { if (firstWaiter.getLocker() == locker) { assert false : "locker " + locker + " is already on waiters list."; } } if (waiterList != null) { Iterator iter=waiterList.iterator(); while (iter.hasNext()) { LockInfo o=(LockInfo)iter.next(); if (o.getLocker() == locker) { assert false : "locker " + locker + " is already on waiters list."; } } } return true; } /** * Debug dumper. */ public String toString(){ StringBuffer sb=new StringBuffer(); sb.append(" NodeId:").append(nodeId); sb.append(" Owners:"); if (nOwners() == 0) { sb.append(" (none)"); } else { if (firstOwner != null) { sb.append(firstOwner); } if (ownerSet != null) { Iterator iter=ownerSet.iterator(); while (iter.hasNext()) { LockInfo info=(LockInfo)iter.next(); sb.append(info); } } } sb.append(" Waiters:"); if (nWaiters() == 0) { sb.append(" (none)"); } else { sb.append(getWaitersListClone()); } return sb.toString(); } protected void hook760( MemoryBudget mb, int lockTableIndex){ } protected void hook761( MemoryBudget mb, int lockTableIndex){ } protected void hook762( MemoryBudget mb, int lockTableIndex){ } protected void hook763( MemoryBudget mb, int lockTableIndex){ } protected void hook764( MemoryBudget mb, int lockTableIndex){ } protected void hook765( MemoryBudget mb, int lockTableIndex, boolean removed){ } protected void hook766( MemoryBudget mb, int lockTableIndex, LockInfo flushedInfo){ } protected void hook767( MemoryBudget mb, int lockTableIndex){ } protected void hook768( MemoryBudget mb, int lockTableIndex, int numRemovedLockInfos) throws DatabaseException { } }