package com.sleepycat.je.txn;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DeadlockException;
import com.sleepycat.je.LockStats;
import com.sleepycat.je.RunRecoveryException;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.EnvConfigObserver;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.dbi.MemoryBudget;
import com.sleepycat.je.dbi.RangeRestartException;
import de.ovgu.cide.jakutil.*;
/**
* LockManager manages locks.
* Note that locks are counted as taking up part of the JE cache;
*/
public abstract class LockManager implements EnvConfigObserver {
protected int nLockTables=1;
private Map[] lockTables;
private EnvironmentImpl envImpl;
private MemoryBudget memoryBudget;
private static RangeRestartException rangeRestartException=new RangeRestartException();
private static boolean lockTableDump=false;
public LockManager( EnvironmentImpl envImpl) throws DatabaseException {
DbConfigManager configMgr=envImpl.getConfigManager();
this.hook779(configMgr);
lockTables=new Map[nLockTables];
this.hook770();
for (int i=0; i < nLockTables; i++) {
lockTables[i]=new HashMap();
this.hook771(envImpl,i);
}
this.envImpl=envImpl;
memoryBudget=envImpl.getMemoryBudget();
this.hook774();
envConfigUpdate(configMgr);
envImpl.addConfigObserver(this);
}
/**
* Process notifications of mutable property changes.
*/
public void envConfigUpdate( DbConfigManager configMgr) throws DatabaseException {
LockInfo.setDeadlockStackTrace(configMgr.getBoolean(EnvironmentParams.TXN_DEADLOCK_STACK_TRACE));
setLockTableDump(configMgr.getBoolean(EnvironmentParams.TXN_DUMPLOCKS));
}
/**
* Called when the je.txn.dumpLocks property is changed.
*/
static void setLockTableDump( boolean enable){
lockTableDump=enable;
}
protected int getLockTableIndex( Long nodeId){
return ((int)nodeId.longValue()) % nLockTables;
}
protected int getLockTableIndex( long nodeId){
return ((int)nodeId) % nLockTables;
}
/**
* Attempt to acquire a lock of <i>type</i> on <i>nodeId</i>. If the lock
* acquisition would result in a deadlock, throw an exception.<br>
* If the requested lock is not currently available, block until it is or
* until timeout milliseconds have elapsed.<br>
* If a lock of <i>type</i> is already held, return EXISTING.<br>
* If a WRITE lock is held and a READ lock is requested, return PROMOTION.<br>
* If a lock request is for a lock that is not currently held, return either
* NEW or DENIED depending on whether the lock is granted or not.<br>
* @param nodeIdThe NodeId to lock.
* @param lockerThe Locker to lock this on behalf of.
* @param typeThe lock type requested.
* @param timeoutmilliseconds to time out after if lock couldn't be obtained. 0
* means block indefinitely. Not used if nonBlockingRequest is
* true.
* @param nonBlockingRequestif true, means don't block if lock can't be acquired, and
* ignore the timeout parameter.
* @return a LockGrantType indicating whether the request was fulfilled or
* not. LockGrantType.NEW means the lock grant was fulfilled and the
* caller did not previously hold the lock. PROMOTION means the lock
* was granted and it was a promotion from READ to WRITE. EXISTING
* means the lock was already granted (not a promotion). DENIED
* means the lock was not granted either because the timeout passed
* without acquiring the lock or timeout was -1 and the lock was not
* immediately available.
* @throws DeadlockExceptionif acquiring the lock would result in a deadlock.
*/
public LockGrantType lock( long nodeId, Locker locker, LockType type, long timeout, boolean nonBlockingRequest, DatabaseImpl database) throws DeadlockException, DatabaseException {
assert timeout >= 0;
synchronized (locker) {
Long nid=new Long(nodeId);
LockAttemptResult result=attemptLock(nid,locker,type,nonBlockingRequest);
if (result.success || result.lockGrant == LockGrantType.DENIED) {
return result.lockGrant;
}
this.hook772(nonBlockingRequest);
assert !nonBlockingRequest;
try {
boolean doWait=true;
if (locker.isTimedOut()) {
if (validateOwnership(nid,locker,type,true,memoryBudget)) {
doWait=false;
}
else {
String errMsg=makeTimeoutMsg("Transaction",locker,nodeId,type,result.lockGrant,result.useLock,locker.getTxnTimeOut(),locker.getTxnStartMillis(),System.currentTimeMillis(),database);
throw new DeadlockException(errMsg);
}
}
boolean keepTime=(timeout > 0);
long startTime=(keepTime ? System.currentTimeMillis() : 0);
while (doWait) {
locker.setWaitingFor(result.useLock);
try {
locker.wait(timeout);
}
catch ( InterruptedException IE) {
throw new RunRecoveryException(envImpl,IE);
}
boolean lockerTimedOut=locker.isTimedOut();
long now=System.currentTimeMillis();
boolean thisLockTimedOut=(keepTime && (now - startTime > timeout));
boolean isRestart=(result.lockGrant == LockGrantType.WAIT_RESTART);
if (validateOwnership(nid,locker,type,lockerTimedOut || thisLockTimedOut || isRestart,memoryBudget)) {
break;
}
else {
if (isRestart) {
throw rangeRestartException;
}
if (thisLockTimedOut) {
locker.setOnlyAbortable();
String errMsg=makeTimeoutMsg("Lock",locker,nodeId,type,result.lockGrant,result.useLock,timeout,startTime,now,database);
throw new DeadlockException(errMsg);
}
if (lockerTimedOut) {
locker.setOnlyAbortable();
String errMsg=makeTimeoutMsg("Transaction",locker,nodeId,type,result.lockGrant,result.useLock,locker.getTxnTimeOut(),locker.getTxnStartMillis(),now,database);
throw new DeadlockException(errMsg);
}
}
}
}
finally {
locker.setWaitingFor(null);
assert EnvironmentImpl.maybeForceYield();
}
locker.addLock(nid,result.useLock,type,result.lockGrant);
return result.lockGrant;
}
}
abstract protected LockAttemptResult attemptLock( Long nodeId, Locker locker, LockType type, boolean nonBlockingRequest) throws DatabaseException ;
protected LockAttemptResult attemptLockInternal( Long nodeId, Locker locker, LockType type, boolean nonBlockingRequest, int lockTableIndex) throws DatabaseException {
Map lockTable=lockTables[lockTableIndex];
Lock useLock=(Lock)lockTable.get(nodeId);
if (useLock == null) {
useLock=new Lock(nodeId);
lockTable.put(nodeId,useLock);
this.hook780(lockTableIndex);
}
LockGrantType lockGrant=useLock.lock(type,locker,nonBlockingRequest,memoryBudget,lockTableIndex);
boolean success=false;
if ((lockGrant == LockGrantType.NEW) || (lockGrant == LockGrantType.PROMOTION)) {
locker.addLock(nodeId,useLock,type,lockGrant);
success=true;
}
else if (lockGrant == LockGrantType.EXISTING) {
success=true;
}
else if (lockGrant == LockGrantType.DENIED) {
}
else {
this.hook775();
}
return new LockAttemptResult(useLock,lockGrant,success);
}
/**
* Create a informative lock or txn timeout message.
*/
protected abstract String makeTimeoutMsg( String lockOrTxn, Locker locker, long nodeId, LockType type, LockGrantType grantType, Lock useLock, long timeout, long start, long now, DatabaseImpl database) throws DatabaseException ;
/**
* Do the real work of creating an lock or txn timeout message.
*/
protected String makeTimeoutMsgInternal( String lockOrTxn, Locker locker, long nodeId, LockType type, LockGrantType grantType, Lock useLock, long timeout, long start, long now, DatabaseImpl database){
if (lockTableDump) {
System.out.println("++++++++++ begin lock table dump ++++++++++");
for (int i=0; i < nLockTables; i++) {
StringBuffer sb=new StringBuffer();
dumpToStringNoLatch(sb,i);
System.out.println(sb.toString());
}
System.out.println("++++++++++ end lock table dump ++++++++++");
}
StringBuffer sb=new StringBuffer();
sb.append(lockOrTxn);
sb.append(" expired. Locker ").append(locker);
sb.append(": waited for lock");
if (database != null) {
sb.append(" on database=").append(database.getDebugName());
}
sb.append(" node=").append(nodeId);
sb.append(" type=").append(type);
sb.append(" grant=").append(grantType);
sb.append(" timeoutMillis=").append(timeout);
sb.append(" startTime=").append(start);
sb.append(" endTime=").append(now);
sb.append("\nOwners: ").append(useLock.getOwnersClone());
sb.append("\nWaiters: ").append(useLock.getWaitersListClone()).append("\n");
StringBuffer deadlockInfo=findDeadlock(useLock,locker);
if (deadlockInfo != null) {
sb.append(deadlockInfo);
}
return sb.toString();
}
/**
* Release a lock and possibly notify any waiters that they have been
* granted the lock.
* @param nodeIdThe node ID of the lock to release.
* @return true if the lock is released successfully, false if the lock is
* not currently being held.
*/
boolean release( long nodeId, Locker locker) throws DatabaseException {
return release(nodeId,null,locker,true);
}
/**
* Release a lock and possibly notify any waiters that they have been
* granted the lock.
* @param lockThe lock to release
* @return true if the lock is released successfully, false if the lock is
* not currently being held.
*/
boolean release( Lock lock, Locker locker) throws DatabaseException {
return release(-1,lock,locker,false);
}
/**
* Do the work of releasing a lock and notifying any waiters that they have
* been granted the lock.
* @param lockThe lock to release. If null, use nodeId to find lock
* @param nodeIdThe node ID of the lock to release, if lock is null. May not
* be valid if lock is not null. MUST be valid if
* removeFromLocker is true
* @param locker
* @param removeFromLockertrue if we're responsible for
* @return true if the lock is released successfully, false if the lock is
* not currently being held.
*/
private boolean release( long nodeId, Lock lock, Locker locker, boolean removeFromLocker) throws DatabaseException {
synchronized (locker) {
Set newOwners=releaseAndFindNotifyTargets(nodeId,lock,locker,removeFromLocker);
if (newOwners == null) {
return false;
}
if (newOwners.size() > 0) {
Iterator iter=newOwners.iterator();
while (iter.hasNext()) {
Locker lockerToNotify=(Locker)iter.next();
synchronized (lockerToNotify) {
lockerToNotify.notifyAll();
}
assert EnvironmentImpl.maybeForceYield();
}
}
return true;
}
}
/**
* Release the lock, and return the set of new owners to notify, if any.
* @return null if the lock does not exist or the given locker was not the
* owner, a non-empty set if owners should be notified after
* releasing, an empty set if no notification is required.
*/
protected abstract Set releaseAndFindNotifyTargets( long nodeId, Lock lock, Locker locker, boolean removeFromLocker) throws DatabaseException ;
/**
* Do the real work of releaseAndFindNotifyTargets
*/
protected Set releaseAndFindNotifyTargetsInternal( long nodeId, Lock lock, Locker locker, boolean removeFromLocker, int lockTableIndex) throws DatabaseException {
Lock useLock=lock;
Map lockTable=lockTables[lockTableIndex];
if (useLock == null) {
useLock=(Lock)lockTable.get(new Long(nodeId));
}
if (useLock == null) {
return null;
}
Set lockersToNotify=useLock.release(locker,memoryBudget,lockTableIndex);
if (lockersToNotify == null) {
return null;
}
if (removeFromLocker) {
assert nodeId != -1;
locker.removeLock(nodeId,useLock);
}
if ((useLock.nWaiters() == 0) && (useLock.nOwners() == 0)) {
lockTables[lockTableIndex].remove(useLock.getNodeId());
this.hook781(lockTableIndex);
}
return lockersToNotify;
}
/**
* Transfer ownership a lock from one locker to another locker. We're not
* sending any notification to the waiters on the lock table, and the past
* and present owner should be ready for the transfer.
*/
abstract void transfer( long nodeId, Locker owningLocker, Locker destLocker, boolean demoteToRead) throws DatabaseException ;
/**
* Do the real work of transfer
*/
protected void transferInternal( long nodeId, Locker owningLocker, Locker destLocker, boolean demoteToRead, int lockTableIndex) throws DatabaseException {
Map lockTable=lockTables[lockTableIndex];
Lock useLock=(Lock)lockTable.get(new Long(nodeId));
assert useLock != null : "Transfer, lock " + nodeId + " was null";
if (demoteToRead) {
useLock.demote(owningLocker);
}
useLock.transfer(owningLocker,destLocker,memoryBudget,lockTableIndex);
owningLocker.removeLock(nodeId,useLock);
}
/**
* Transfer ownership a lock from one locker to a set of other txns, cloning
* the lock as necessary. This will always be demoted to read, as we can't
* have multiple locker owners any other way. We're not sending any
* notification to the waiters on the lock table, and the past and present
* owners should be ready for the transfer.
*/
abstract void transferMultiple( long nodeId, Locker owningLocker, Locker[] destLockers) throws DatabaseException ;
/**
* Do the real work of transferMultiple
*/
protected void transferMultipleInternal( long nodeId, Locker owningLocker, Locker[] destLockers, int lockTableIndex) throws DatabaseException {
Map lockTable=lockTables[lockTableIndex];
Lock useLock=(Lock)lockTable.get(new Long(nodeId));
assert useLock != null : "Transfer, lock " + nodeId + " was null";
useLock.demote(owningLocker);
useLock.transferMultiple(owningLocker,destLockers,memoryBudget,lockTableIndex);
owningLocker.removeLock(nodeId,useLock);
}
/**
* Demote a lock from write to read. Call back to the owning locker to move
* this to its read collection.
* @param lockThe lock to release. If null, use nodeId to find lock
* @param locker
*/
abstract void demote( long nodeId, Locker locker) throws DatabaseException ;
/**
* Do the real work of demote.
*/
protected void demoteInternal( long nodeId, Locker locker, int lockTableIndex) throws DatabaseException {
Map lockTable=lockTables[lockTableIndex];
Lock useLock=(Lock)lockTable.get(new Long(nodeId));
useLock.demote(locker);
locker.moveWriteToReadLock(nodeId,useLock);
}
/**
* Test the status of the lock on nodeId. If any transaction holds any lock
* on it, true is returned. If no transaction holds a lock on it, false is
* returned.
* This method is only used by unit tests.
* @param nodeIdThe NodeId to check.
* @return true if any transaction holds any lock on the nodeid. false if no
* lock is held by any transaction.
*/
abstract boolean isLocked( Long nodeId) throws DatabaseException ;
/**
* Do the real work of isLocked.
*/
protected boolean isLockedInternal( Long nodeId, int lockTableIndex){
Map lockTable=lockTables[lockTableIndex];
Lock entry=(Lock)lockTable.get(nodeId);
if (entry == null) {
return false;
}
return entry.nOwners() != 0;
}
/**
* Return true if this locker owns this a lock of this type on given node.
* This method is only used by unit tests.
*/
abstract boolean isOwner( Long nodeId, Locker locker, LockType type) throws DatabaseException ;
/**
* Do the real work of isOwner.
*/
protected boolean isOwnerInternal( Long nodeId, Locker locker, LockType type, int lockTableIndex){
Map lockTable=lockTables[lockTableIndex];
Lock entry=(Lock)lockTable.get(nodeId);
if (entry == null) {
return false;
}
return entry.isOwner(locker,type);
}
/**
* Return true if this locker is waiting on this lock.
* This method is only used by unit tests.
*/
abstract boolean isWaiter( Long nodeId, Locker locker) throws DatabaseException ;
/**
* Do the real work of isWaiter.
*/
protected boolean isWaiterInternal( Long nodeId, Locker locker, int lockTableIndex){
Map lockTable=lockTables[lockTableIndex];
Lock entry=(Lock)lockTable.get(nodeId);
if (entry == null) {
return false;
}
return entry.isWaiter(locker);
}
/**
* Return the number of waiters for this lock.
*/
abstract int nWaiters( Long nodeId) throws DatabaseException ;
/**
* Do the real work of nWaiters.
*/
protected int nWaitersInternal( Long nodeId, int lockTableIndex){
Map lockTable=lockTables[lockTableIndex];
Lock entry=(Lock)lockTable.get(nodeId);
if (entry == null) {
return -1;
}
return entry.nWaiters();
}
/**
* Return the number of owners of this lock.
*/
abstract int nOwners( Long nodeId) throws DatabaseException ;
/**
* Do the real work of nWaiters.
*/
protected int nOwnersInternal( Long nodeId, int lockTableIndex){
Map lockTable=lockTables[lockTableIndex];
Lock entry=(Lock)lockTable.get(nodeId);
if (entry == null) {
return -1;
}
return entry.nOwners();
}
/**
* @return the transaction that owns the write lock for this
*/
abstract Locker getWriteOwnerLocker( Long nodeId) throws DatabaseException ;
/**
* Do the real work of getWriteOwnerLocker.
*/
protected Locker getWriteOwnerLockerInternal( Long nodeId, int lockTableIndex) throws DatabaseException {
Map lockTable=lockTables[lockTableIndex];
Lock lock=(Lock)lockTable.get(nodeId);
if (lock == null) {
return null;
}
else if (lock.nOwners() > 1) {
return null;
}
else {
return lock.getWriteOwnerLocker();
}
}
abstract protected boolean validateOwnership( Long nodeId, Locker locker, LockType type, boolean flushFromWaiters, MemoryBudget mb) throws DatabaseException ;
protected boolean validateOwnershipInternal( Long nodeId, Locker locker, LockType type, boolean flushFromWaiters, MemoryBudget mb, int lockTableIndex) throws DatabaseException {
if (isOwnerInternal(nodeId,locker,type,lockTableIndex)) {
return true;
}
if (flushFromWaiters) {
Lock entry=(Lock)lockTables[lockTableIndex].get(nodeId);
if (entry != null) {
entry.flushWaiter(locker,mb,lockTableIndex);
}
}
return false;
}
/**
* Dump the lock table to the lock stats.
*/
abstract protected void dumpLockTable( LockStats stats) throws DatabaseException ;
/**
* Do the real work of dumpLockTableInternal.
*/
protected void dumpLockTableInternal( LockStats stats, int i){
Map lockTable=lockTables[i];
this.hook776(stats,lockTable);
Iterator iter=lockTable.values().iterator();
while (iter.hasNext()) {
Lock lock=(Lock)iter.next();
this.hook777(stats,lock);
Iterator ownerIter=lock.getOwnersClone().iterator();
while (ownerIter.hasNext()) {
LockInfo info=(LockInfo)ownerIter.next();
this.hook778(stats,info);
}
}
}
/**
* Debugging
*/
public void dump() throws DatabaseException {
System.out.println(dumpToString());
}
public String dumpToString() throws DatabaseException {
StringBuffer sb=new StringBuffer();
for (int i=0; i < nLockTables; i++) {
this.hook773(sb,i);
}
return sb.toString();
}
private void dumpToStringNoLatch( StringBuffer sb, int whichTable){
Map lockTable=lockTables[whichTable];
Iterator entries=lockTable.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry=(Map.Entry)entries.next();
Long nid=(Long)entry.getKey();
Lock lock=(Lock)entry.getValue();
sb.append("---- Node Id: ").append(nid).append("----\n");
sb.append(lock);
sb.append('\n');
}
}
private StringBuffer findDeadlock( Lock lock, Locker rootLocker){
Set ownerSet=new HashSet();
ownerSet.add(rootLocker);
StringBuffer ret=findDeadlock1(ownerSet,lock,rootLocker);
if (ret != null) {
return ret;
}
else {
return null;
}
}
private StringBuffer findDeadlock1( Set ownerSet, Lock lock, Locker rootLocker){
Iterator ownerIter=lock.getOwnersClone().iterator();
while (ownerIter.hasNext()) {
LockInfo info=(LockInfo)ownerIter.next();
Locker locker=info.getLocker();
Lock waitsFor=locker.getWaitingFor();
if (ownerSet.contains(locker) || locker == rootLocker) {
StringBuffer ret=new StringBuffer();
ret.append("Transaction ").append(locker.toString());
ret.append(" owns ").append(lock.getNodeId());
ret.append(" ").append(info).append("\n");
ret.append("Transaction ").append(locker.toString());
ret.append(" waits for ");
if (waitsFor == null) {
ret.append(" nothing");
}
else {
ret.append(" node ");
ret.append(waitsFor.getNodeId());
}
ret.append("\n");
return ret;
}
if (waitsFor != null) {
ownerSet.add(locker);
StringBuffer sb=findDeadlock1(ownerSet,waitsFor,rootLocker);
if (sb != null) {
String waitInfo="Transaction " + locker + " waits for node "+ waitsFor.getNodeId()+ "\n";
sb.insert(0,waitInfo);
return sb;
}
ownerSet.remove(locker);
}
}
return null;
}
/**
* This is just a struct to hold a multi-value return.
*/
static class LockAttemptResult {
boolean success;
Lock useLock;
LockGrantType lockGrant;
LockAttemptResult( Lock useLock, LockGrantType lockGrant, boolean success){
this.useLock=useLock;
this.lockGrant=lockGrant;
this.success=success;
}
}
protected void hook770() throws DatabaseException {
}
protected void hook771( EnvironmentImpl envImpl, int i) throws DatabaseException {
}
protected void hook772( boolean nonBlockingRequest) throws DeadlockException, DatabaseException {
}
protected void hook773( StringBuffer sb, int i) throws DatabaseException {
dumpToStringNoLatch(sb,i);
}
protected void hook774() throws DatabaseException {
}
protected void hook775() throws DatabaseException {
}
protected void hook776( LockStats stats, Map lockTable){
}
protected void hook777( LockStats stats, Lock lock){
}
protected void hook778( LockStats stats, LockInfo info){
}
protected void hook779( DbConfigManager configMgr) throws DatabaseException {
}
protected void hook780( int lockTableIndex) throws DatabaseException {
}
protected void hook781( int lockTableIndex) throws DatabaseException {
}
}