package com.sleepycat.je.txn;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DeadlockException;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.LockNotGrantedException;
import com.sleepycat.je.LockStats;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.dbi.CursorImpl;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.tree.BIN;
import com.sleepycat.je.tree.BINReference;
import com.sleepycat.je.tree.Key;
import de.ovgu.cide.jakutil.*;
/**
* Locker instances are JE's route to locking and transactional support. This
* class is the abstract base class for BasicLocker, ThreadLocker, Txn and
* AutoTxn. Locker instances are in fact only a transaction shell to get to
* the lock manager, and don't guarantee transactional semantics. Txn and
* AutoTxn instances are both truely transactional, but have different ending
* behaviors.
*/
public abstract class Locker {
private static final String DEBUG_NAME=Locker.class.getName();
protected EnvironmentImpl envImpl;
protected LockManager lockManager;
protected long id;
protected boolean readUncommittedDefault;
protected boolean defaultNoWait;
protected long lockTimeOutMillis;
private long txnTimeOutMillis;
private long txnStartMillis;
private Lock waitingFor;
protected Map handleLockToHandleMap;
protected Map handleToHandleLockMap;
/**
* The thread that created this locker. Used for debugging, and by the
* ThreadLocker subclass. Note that thread may be null if the Locker is
* instantiated by reading the log.
*/
protected Thread thread;
/**
* Create a locker id. This constructor is called very often, so it should
* be as streamlined as possible.
* @param lockManager lock manager for this environment
* @param readUncommittedDefault if true, this transaction does
* read-uncommitted by default
* @param noWait if true, non-blocking lock requests are used.
*/
public Locker( EnvironmentImpl envImpl, boolean readUncommittedDefault, boolean noWait) throws DatabaseException {
TxnManager txnManager=envImpl.getTxnManager();
this.id=generateId(txnManager);
this.envImpl=envImpl;
lockManager=txnManager.getLockManager();
this.readUncommittedDefault=readUncommittedDefault;
this.waitingFor=null;
defaultNoWait=noWait;
lockTimeOutMillis=envImpl.getLockTimeout();
txnTimeOutMillis=envImpl.getTxnTimeout();
if (txnTimeOutMillis != 0) {
txnStartMillis=System.currentTimeMillis();
}
else {
txnStartMillis=0;
}
thread=Thread.currentThread();
}
/**
* For reading from the log.
*/
Locker(){
}
/**
* A Locker has to generate its next id. Some subtypes, like BasicLocker,
* have a single id for all instances because they are never used for
* recovery. Other subtypes ask the txn manager for an id.
*/
protected abstract long generateId( TxnManager txnManager);
/**
* @return the transaction's id.
*/
public long getId(){
return id;
}
/**
* @return the default no-wait (non-blocking) setting.
*/
public boolean getDefaultNoWait(){
return defaultNoWait;
}
/**
* Get the lock timeout period for this transaction, in milliseconds
*/
public synchronized long getLockTimeout(){
return lockTimeOutMillis;
}
/**
* Set the lock timeout period for any locks in this transaction,
* in milliseconds.
*/
public synchronized void setLockTimeout( long timeOutMillis){
lockTimeOutMillis=timeOutMillis;
}
/**
* Set the timeout period for this transaction, in milliseconds.
*/
public synchronized void setTxnTimeout( long timeOutMillis){
txnTimeOutMillis=timeOutMillis;
txnStartMillis=System.currentTimeMillis();
}
/**
* @return true if transaction was created with read-uncommitted as a
* default.
*/
public boolean isReadUncommittedDefault(){
return readUncommittedDefault;
}
Lock getWaitingFor(){
return waitingFor;
}
void setWaitingFor( Lock lock){
waitingFor=lock;
}
/**
* Set the state of a transaction to ONLY_ABORTABLE.
*/
void setOnlyAbortable(){
}
protected abstract void checkState( boolean ignoreCalledByAbort) throws DatabaseException ;
/**
* Abstract method to a blocking or non-blocking lock of the given type on
* the given nodeId. Unlike the lock() method, this method does not throw
* LockNotGrantedException and can therefore be used by nonBlockingLock to
* probe for a lock without the overhead of an exception stack trace.
* @param nodeId is the node to lock.
* @param lockType is the type of lock to request.
* @param noWait is true to override the defaultNoWait setting. If true,
* or if defaultNoWait is true, throws LockNotGrantedException if the lock
* cannot be granted without waiting.
* @param database is the database containing nodeId.
* @throws DeadlockException if acquiring a blocking lock would result in a
* deadlock.
*/
abstract LockResult lockInternal( long nodeId, LockType lockType, boolean noWait, DatabaseImpl database) throws DeadlockException, DatabaseException ;
/**
* Request a blocking or non-blocking lock of the given type on the given
* nodeId.
* @param nodeId is the node to lock.
* @param lockType is the type of lock to request.
* @param noWait is true to override the defaultNoWait setting. If true,
* or if defaultNoWait is true, throws LockNotGrantedException if the lock
* cannot be granted without waiting.
* @param database is the database containing nodeId.
* @throws LockNotGrantedException if a non-blocking lock was denied.
* @throws DeadlockException if acquiring a blocking lock would result in a
* deadlock.
*/
public LockResult lock( long nodeId, LockType lockType, boolean noWait, DatabaseImpl database) throws LockNotGrantedException, DeadlockException, DatabaseException {
LockResult result=lockInternal(nodeId,lockType,noWait,database);
if (result.getLockGrant() == LockGrantType.DENIED) {
throw new LockNotGrantedException("Non-blocking lock was denied.");
}
else {
return result;
}
}
/**
* Request a non-blocking lock of the given type on the given nodeId.
* <p>Unlike lock(), this method returns LockGrantType.DENIED if the lock
* is denied rather than throwing LockNotGrantedException. This method
* should therefore not be used as the final lock for a user operation,
* since in that case LockNotGrantedException should be thrown for a denied
* lock. It is normally used only to probe for a lock, and other recourse
* is taken if the lock is denied.</p>
* @param nodeId is the node to lock.
* @param lockType is the type of lock to request.
* @param database is the database containing nodeId.
*/
public LockResult nonBlockingLock( long nodeId, LockType lockType, DatabaseImpl database) throws DatabaseException {
return lockInternal(nodeId,lockType,true,database);
}
/**
* Release the lock on this LN and remove from the transaction's owning
* set.
*/
public void releaseLock( long nodeId) throws DatabaseException {
lockManager.release(nodeId,this);
}
/**
* Revert this lock from a write lock to a read lock.
*/
public void demoteLock( long nodeId) throws DatabaseException {
lockManager.demote(nodeId,this);
}
/**
* Returns whether this locker is transactional.
*/
public abstract boolean isTransactional();
/**
* Returns whether the isolation level of this locker is serializable.
*/
public abstract boolean isSerializableIsolation();
/**
* Returns whether the isolation level of this locker is read-committed.
*/
public abstract boolean isReadCommittedIsolation();
/**
* Returns the underlying Txn if the locker is transactional, or null if
* the locker is non-transactional. For a Txn-based locker, this method
* returns 'this'. For a BuddyLocker, this method may returns the buddy.
*/
public abstract Txn getTxnLocker();
/**
* Creates a fresh non-transactional locker, while retaining any
* transactional locks held by this locker. This method is called when the
* cursor for this locker is cloned.
* <p>In general, transactional lockers return 'this' when this method is
* called, while non-transactional lockers return a new instance.</p>
*/
public abstract Locker newNonTxnLocker() throws DatabaseException ;
/**
* Releases any non-transactional locks held by this locker. This method
* is called when the cursor moves to a new position or is closed.
* <p>In general, transactional lockers do nothing when this method is
* called, while non-transactional lockers release all locks as if
* operationEnd were called.</p>
*/
public abstract void releaseNonTxnLocks() throws DatabaseException ;
/**
* Returns whether this locker can share locks with the given locker.
* <p>All lockers share locks with a BuddyLocker whose buddy is this
* locker. To support BuddyLocker when overriding this method, always
* return true if this implementation (super.sharesLocksWith(...)) returns
* true.</p>
*/
public boolean sharesLocksWith( Locker other){
if (other instanceof BuddyLocker) {
BuddyLocker buddy=(BuddyLocker)other;
return buddy.getBuddy() == this;
}
else {
return false;
}
}
/**
* The equivalent of calling operationEnd(true).
*/
public abstract void operationEnd() throws DatabaseException ;
/**
* Different types of transactions do different things when the operation
* ends. Txns do nothing, AutoTxns commit or abort, and BasicLockers and
* ThreadLockers just release locks.
* @param operationOK is whether the operation succeeded, since
* that may impact ending behavior. (i.e for AutoTxn)
*/
public abstract void operationEnd( boolean operationOK) throws DatabaseException ;
/**
* We're at the end of an operation. Move this handle lock to the
* appropriate owner.
*/
public abstract void setHandleLockOwner( boolean operationOK, Database dbHandle, boolean dbIsClosing) throws DatabaseException ;
/**
* A SUCCESS status equals operationOk.
*/
public void operationEnd( OperationStatus status) throws DatabaseException {
operationEnd(status == OperationStatus.SUCCESS);
}
/**
* Tell this transaction about a cursor.
*/
public abstract void registerCursor( CursorImpl cursor) throws DatabaseException ;
/**
* Remove a cursor from this txn.
*/
public abstract void unRegisterCursor( CursorImpl cursor) throws DatabaseException ;
/**
* @return the abort LSN for this node.
*/
public abstract long getAbortLsn( long nodeId) throws DatabaseException ;
/**
* @return the WriteLockInfo for this node.
*/
public abstract WriteLockInfo getWriteLockInfo( long nodeId) throws DatabaseException ;
/**
* Add a lock to set owned by this transaction.
*/
abstract void addLock( Long nodeId, Lock lock, LockType type, LockGrantType grantStatus) throws DatabaseException ;
/**
* @return true if this transaction created this node,
* for a operation with transactional semantics.
*/
public abstract boolean createdNode( long nodeId) throws DatabaseException ;
/**
* Remove the lock from the set owned by this transaction. If specified to
* LockManager.release, the lock manager will call this when its releasing
* a lock.
*/
abstract void removeLock( long nodeId, Lock lock) throws DatabaseException ;
/**
* A lock is being demoted. Move it from the write collection into the read
* collection.
*/
abstract void moveWriteToReadLock( long nodeId, Lock lock);
boolean isTimedOut() throws DatabaseException {
if (txnStartMillis != 0) {
long diff=System.currentTimeMillis() - txnStartMillis;
if (diff > txnTimeOutMillis) {
return true;
}
}
return false;
}
public long getTxnTimeOut(){
return txnTimeOutMillis;
}
long getTxnStartMillis(){
return txnStartMillis;
}
/**
* Remove this Database from the protected Database handle set
*/
void unregisterHandle( Database dbHandle){
if (handleToHandleLockMap != null) {
handleToHandleLockMap.remove(dbHandle);
}
}
/**
* Remember how handle locks and handles match up.
*/
public void addToHandleMaps( Long handleLockId, Database databaseHandle){
Set dbHandleSet=null;
if (handleLockToHandleMap == null) {
handleLockToHandleMap=new Hashtable();
handleToHandleLockMap=new Hashtable();
}
else {
dbHandleSet=(Set)handleLockToHandleMap.get(handleLockId);
}
if (dbHandleSet == null) {
dbHandleSet=new HashSet();
handleLockToHandleMap.put(handleLockId,dbHandleSet);
}
dbHandleSet.add(databaseHandle);
handleToHandleLockMap.put(databaseHandle,handleLockId);
}
/**
* @return true if this txn is willing to give up the handle lock to
* another txn before this txn ends.
*/
public boolean isHandleLockTransferrable(){
return true;
}
/**
* The currentTxn passes responsiblity for this db handle lock to a txn
* owned by the Database object.
*/
void transferHandleLockToHandle( Database dbHandle) throws DatabaseException {
Locker holderTxn=new BasicLocker(envImpl);
transferHandleLock(dbHandle,holderTxn,true);
}
/**
*/
public void transferHandleLock( Database dbHandle, Locker destLocker, boolean demoteToRead) throws DatabaseException {
if (DbInternal.dbGetDatabaseImpl(dbHandle) != null) {
Long handleLockId=(Long)handleToHandleLockMap.get(dbHandle);
if (handleLockId != null) {
long nodeId=handleLockId.longValue();
lockManager.transfer(nodeId,this,destLocker,demoteToRead);
destLocker.addToHandleMaps(handleLockId,dbHandle);
Set dbHandleSet=(Set)handleLockToHandleMap.get(handleLockId);
Iterator iter=dbHandleSet.iterator();
while (iter.hasNext()) {
if (((Database)iter.next()) == dbHandle) {
iter.remove();
break;
}
}
if (dbHandleSet.size() == 0) {
handleLockToHandleMap.remove(handleLockId);
}
DbInternal.dbSetHandleLocker(dbHandle,destLocker);
}
}
}
/**
* If necessary, remember that this txn once owned a handle lock. Done to
* make commit optimizations work correctly.
*/
protected void rememberHandleWriteLock( Long lockId){
}
public String toString(){
String className=getClass().getName();
className=className.substring(className.lastIndexOf('.') + 1);
return Long.toString(id) + "_" + ((thread == null) ? "" : thread.getName())+ "_"+ className;
}
/**
* Dump lock table, for debugging
*/
public void dumpLockTable() throws DatabaseException {
lockManager.dump();
}
}