/*
* Copyright (C) 2006 http://www.chaidb.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
/* Generated by Together */
package org.chaidb.db.transaction;
import org.apache.log4j.Logger;
import org.chaidb.db.Db;
import org.chaidb.db.DbEnvironment;
import org.chaidb.db.DBState;
import org.chaidb.db.exception.ChaiDBException;
import org.chaidb.db.exception.ErrorCode;
import org.chaidb.db.helper.Config;
import org.chaidb.db.helper.DaemonThreadManager;
import org.chaidb.db.helper.Queue;
import org.chaidb.db.lock.LockManager;
import org.chaidb.db.log.LogManager;
import org.chaidb.db.log.Lsn;
import org.chaidb.db.log.MemLogManager;
import org.chaidb.db.transaction.recover.CatastrophicTxnRecoverImpl;
import org.chaidb.db.transaction.recover.NormalTransactionRecoverImpl;
import org.chaidb.db.transaction.recover.TransactionRecover;
import java.util.Enumeration;
import java.util.Hashtable;
public class TransactionManagerImpl implements TransactionManager {
private static final Logger logger = Logger.getLogger(TransactionManagerImpl.class);
/*
* List of active transactions.
*/
private Hashtable txnChain;
/*
* Free transaction pool.
*/
private Queue freePool;
/* Singleton instance. */
private static TransactionManagerImpl txnManagerImpl = null;
/**
* The maximum number of transactions possible.
* Conflict with maxnActiveTxns ?!
*/
private int maxTxns;
/*
* The flags of the transaction manager.
*/
private static int flags;
private Checkpoint ckpObj;
/*
* The time of the last completed checkpoint finished.
*/
private long timeCkp;
/*
* The lock manager related to the transaction manager.
*/
protected LockManager lockManager;
/* The log manager related to the transaction manager. */
protected LogManager logManager;
/* The memory log manager related to the transaction manager. */
protected MemLogManager memLogManager;
/* The maximum number of active transactions at any one time. */
private int maxnActiveTxns;
/*
* The last transaction ID allocated.
*/
private int lastTxnId;
/*
* The Lsn of the last checkpoint.*/
private Lsn lastCkp;
/* The number of active TXNs */
volatile int nActiveTxns;
/* The number of begun TXNs */
private int nBegunTxns;
/* The number of committed TXNs */
private int nCommittedTxns;
/* number of restored TXNs */
//private int nRestoredTxns;
/* The number of aborted TXNs */
private int nAbortedTxns;
private TransactionRecover txnRecover;
//static Object checkpointOjb = new Object();
/* file separator */
private static final int CKP_DEFAULT_KBYTES = 1000;
private static final int CKP_DEFAULT_MINS = 5;
private static final int CKP_KBYTES = Config.getConfig("checkpoint.kbytes", CKP_DEFAULT_KBYTES);
private static final int CKP_MINS = Config.getConfig("checkpoint.mins", CKP_DEFAULT_MINS);
/**
* Private Constructor.
*/
private TransactionManagerImpl() {
init(DEF_MAX_TXNS);
}
/**
* Singleton method.
*
* @return The unique TransactionManagerImpl instance.
*/
public static TransactionManager getInstance() {
if (txnManagerImpl == null) {
txnManagerImpl = new TransactionManagerImpl(); //only callable from within
return txnManagerImpl;
} else return txnManagerImpl;
//throw new ChaiDBException("no further instances");
}
/**
* Gets the free transaction pool.
*
* @return The free transaction pool.
*/
public Queue getFreePool() {
return freePool;
}
/**
* Gets the maximum number of transactions.
*
* @return The maximum number of transactions.
*/
public int getMaxTxns() {
return maxTxns;
}
/**
* Sets the maximum number of transactions.
*
* @param maxTxns The maximum number of transactions.
*/
public void setMaxTxn(int maxTxns) {
this.maxTxns = maxTxns;
}
/**
* Begin a transaction, return a transaction object.
*
* @param parent The parent transaction. If the parent argument is non-null,
* the new transaction will be a nested transaction, with the transaction
* indicated by parent as its parent. Transactions may be nested to any level.
* @param flags The flags parameter must be set to 0 or one of the following
* values:
* TXN_NOSYNC
* Do not synchronously flush the log when this transaction commits or
* prepares. This means the transaction will exhibit the ACI (atomicity,
* consistency and isolation) properties, but not D (durability), i.e.,
* database integrity will be maintained but it is possible that this
* transaction may be undone during recovery instead of being redone.
* TXN_NOWAIT
* If a lock is unavailable for any operation performed in the context
* of this transaction, return immediately instead of blocking on the
* lock. The exception throw in the case will be LockNotGrantedException.
* TXN_SYNC
* Synchronously flush the log when this transaction commits or prepares.
* This means the transaction will exhibit all of the ACID (atomicity,
* consistency and isolation and durability) properties.
* @return A transaction object
*/
public synchronized Transaction beginTxn(Transaction parent, int flags) throws ChaiDBException {
TransactionImpl txn = null;
//int ilockerId;
//Lock lock = null;
//Judge whether system are doing checkpoint.
//ilockerId = lockManager.id();
//lock = lockManager.get(ilockerId, lockManager.LOCK_WAITING, lockManager.OBJECT_OBJECT, checkpointOjb, lockManager.LOCK_READ);
//lockManager.put(lock);
if (DbEnvironment.READ_ONLY) throw new ChaiDBException(ErrorCode.DATABASE_IS_READONLY);
//I don't know whether the logic is right ? Check it when release.
if (flagCheck(flags, DB_TXN_NOWAIT | DB_TXN_NOSYNC | DB_TXN_SYNC))
throw new ChaiDBException(ErrorCode.ILLEGAL_FLAG_BEGIN_TXN);
if (flagCombinationCheck(flags, DB_TXN_NOSYNC, DB_TXN_SYNC) == false)
throw new ChaiDBException(ErrorCode.ILLEGAL_COMBINATION_FLAG_BEGIN_TXN);
//if ( txnChain.size() >= maxTxns)
// throw new ChaiDBException(ErrorCode.MAXIMUM_ACTIVE_TXNS, "The number of transaction is reach maximum.");
if (freePool.isEmpty()) { //Free transaction pool is empty.
txn = new TransactionImpl(this); //Allocate memory from heap.
} else {
txn = (TransactionImpl) freePool.remove(); //Removes an element from the pool.
}
/* Sets the flags of transaction. */
if ((flags & Transaction.TXN_DIRTY_READ) != 0) txn.setFlags(Transaction.TXN_DIRTY_READ);
if ((flags & Transaction.TXN_NOSYNC) != 0) txn.setFlags(Transaction.TXN_NOSYNC);
if ((flags & Transaction.TXN_SYNC) != 0) txn.setFlags(Transaction.TXN_SYNC);
if ((flags & Transaction.TXN_NOWAIT) != 0) txn.setFlags(Transaction.TXN_NOWAIT);
/*
* We do not have to write begin records (and if we do not, then we
* need never write records for read-only transactions). However,
* we do need to find the current LSN so that we can store it in the
* transaction structure, so we can know where to take checkpoints.
* Note: Here Zhu Liang will put the value into the beginLsn field of the begun transaction instance.
*/
/* Environment is being recovered by recover routine. Note: <this.flags> */
if ((TransactionManagerImpl.flags & TXN_IN_RECOVERY) != 0) {
throw new ChaiDBException(ErrorCode.IN_RECOVER_PROCESS);
}
/* Make sure that we aren't still recovering prepared transactions. Using only during XA recovery.*/
//if (nRestoredTxns != 0){
// throw new ChaiDBException("txn_begin: recovery of prepared but not yet committed transactions is incomplete.");
//}
if (this.lastTxnId == TXN_INVALID_ID)
//throw new ChaiDBException("Transaction ID wrapped. Exit the database environment.\n and restart the application as if application failure had occurred");
this.lastTxnId = TXN_MINIMUM;
//Pending I don't know if cast to TransactionImpl ?!
txn.setParent((TransactionImpl) parent);
txn.setTxnId(lastTxnId++);
saveEnvironment();
txn.setStatus(Transaction.TXN_RUNNING);
/* This statistic information set back 'zero' when system restart.*/
nBegunTxns++;
++nActiveTxns;
if (nActiveTxns > maxnActiveTxns) maxnActiveTxns = nActiveTxns;
/* Initialize last LSN to 'zero'. */
txn.getLastLsn().setFileId(LogManager.INVALID_LSN_FILE_ID);
txn.getLastLsn().setOffset(LogManager.INVALID_LSN_OFFSET);
/*
* If this is a transaction family, we must link the child to the
* maximal grandparent in the lock table for deadlock detection.
* pending!?
*/
//if (txn.getParent() != null)
// if (lockManager.lock_addfamilyLocker(txn.getParent().getTxnId(),txn.getTxnId()) == false)
//Pending?! I don't know if infect the system when return false, and I don't know it is necessary to throw exception.
if (txn != null && txn.getParent() != null) txn.getParent().getChilds().put(new Integer(txn.getTxnId()), txn);
/* Place transaction on active transaction list. */
txnChain.put(new Integer(txn.getTxnId()), txn);
return txn;
}
/**
* General flags checking routine.
*/
private boolean flagCheck(int flags, int ok_flags) {
if ((flags & (~ok_flags)) != 0) return false;
else return true;
}
/**
* General combination flags checking routine.
*/
private boolean flagCombinationCheck(int flags, int flag1, int flag2) {
if ((flags & flag1) != 0 && (flags & flag2) != 0) return false;
else return true; //not conflict
}
/**
* Active TXN list.
*
* @return The active transaction hashtable.
*/
public Hashtable getTxnChain() {
return this.txnChain;
}
/**
* Gets the number of aborted TXNs.
*
* @return The number of aborted TXNs.
*/
public int getAbortedTxns() {
return nAbortedTxns;
}
/**
* Set the number of aborted TXNs.
*
* @param abortedTxns The new number of aborted TXNs.
*/
public void setAbortedTxns(int abortedTxns) {
nAbortedTxns = abortedTxns;
}
/**
* Gets the number of committed TXNs.
*
* @return The number of committed TXNs.
*/
public int getCommittedTxns() {
return nCommittedTxns;
}
/**
* Set the number of committed TXNs.
*
* @param committedTxns The new number of committed TXNs.
*/
public void setCommittedTxns(int committedTxns) {
nCommittedTxns = committedTxns;
}
/**
* Gets the number of begun TXNs.
*
* @return The number of begun TXNs.
*/
public int getBegunTxns() {
return nBegunTxns;
}
/**
* Gets the number of active TXNs.
*
* @return The number of active TXNs.
*/
public int getActiveTxns() {
return nActiveTxns;
}
/*
* Gets the lock manager related to the transaction manager.
*/
public LockManager getLockManager() {
return this.lockManager;
}
/**
* Checkpoint the transaction subsystem - flushes the underlying memory pool,
* writes a checkpoint record to the log and then flushes the log.
* <p>If either kbyte or min is non-zero, the checkpoint is only done if there
* has been activity since the last checkpoint and either more than min
* minutes have passed since the last checkpoint, or if more than kbyte
* kilobytes of log data have been written since the last checkpoint.
*
* @param kbytes
* @param mins
* @param flags The flags parameter must be set to 0 or one of the following
* values:
* DB_FORCE
* Force a checkpoint record even if there has been no activity since
* the last checkpoint.
*/
public void checkpoint(int kbytes, int mins, int flags) throws ChaiDBException {
CheckpointThread ckpThread = (CheckpointThread) DaemonThreadManager.getInstance().getThread(CheckpointThread.CHECKPOINT_THREADNAME);
if (ckpThread == null) {
logger.info("Begin to start checkpoint thread.");
ckpThread = new CheckpointThread(this, kbytes, mins, flags);
ckpThread.start();
} else {
if (ckpThread.isAlive()) {
ckpThread.setKbytes(kbytes);
ckpThread.setMins(mins);
ckpThread.setFlags(flags);
}
}
}
/**
* Just for test!?
*/
public boolean isCheckpointAlive() {
CheckpointThread ckpThread = (CheckpointThread) DaemonThreadManager.getInstance().getThread(CheckpointThread.CHECKPOINT_THREADNAME);
if (ckpThread != null) {
if (ckpThread.isAlive()) return true;
else return false;
} else return false;
}
/**
* Initialize routine.
*/
void init(int maxTxns) {
this.maxTxns = maxTxns;
//this.lastTxnId = TXN_MINIMUM;
loadEnvironment();
txnChain = new Hashtable(maxTxns);
freePool = new Queue();
/* Initialize lastCkp Lsn to 'zero'. */
lastCkp = new Lsn(LogManager.FIRSTREC_LSN);
timeCkp = 0;
nAbortedTxns = 0;
nCommittedTxns = 0;
nBegunTxns = 0;
nActiveTxns = 0;
maxnActiveTxns = 0;
lockManager = Db.getLockManager();
logManager = Db.getLogManager();
memLogManager = Db.getMemLogManager();
startupCkp();
}
public void startupCkp() {
if (Config.TXN_SUPPORT) {
if (CKP_KBYTES != 0 || CKP_MINS != 0) {
try {
this.checkpoint(CKP_KBYTES, CKP_MINS, DB_PERIOD);
} catch (ChaiDBException e) {
logger.error(e);
}
}
}
}
public Transaction getActiveTxn(int txnId) {
return (Transaction) txnChain.get(new Integer(txnId));
}
public TransactionImpl getTxn(int txnId) {
return (TransactionImpl) txnChain.get(new Integer(txnId));
}
public LogManager getLogManager() {
return this.logManager;
}
public MemLogManager getMemLogManager() {
return this.memLogManager;
}
public synchronized int getFlags() {
return flags;
}
public synchronized void setFlags(int argFlags) {
flags |= argFlags;
}
public synchronized void clearFlags(int flags) {
TransactionManagerImpl.flags &= ~(flags);
}
public boolean isOnRecovery() {
if ((flags & TXN_IN_RECOVERY) != 0) {
return true;
} else return false;
}
/**
* Set transaction abort recover function
*
* @param type the type parameter can be one of the following values
* TransactionRecover.CATASTROPHIC_RECOVER or TransactionRecover.NORMAL_RECOVER
* @return The reference of TransactionRecover instance implements TransactionRecover interface.
*/
public TransactionRecover setRecover(int type) {
if (txnRecover == null) {
if (type == TransactionRecover.NORMAL_RECOVER) {
txnRecover = new NormalTransactionRecoverImpl(this);
} else if (type == TransactionRecover.CATASTROPHIC_RECOVER) {
txnRecover = new CatastrophicTxnRecoverImpl(this);
}
}
return txnRecover;
}
/**
* Gets the Lsn of the last checkpoint.
*
* @return The Lsn of the last checkpoint.
*/
public Lsn getLastCkp() {
return new Lsn(this.lastCkp);
}
/**
* Sets the Lsn of the last checkpoint.
*
* @param lastCkp The Lsn of the last checkpoint.
*/
public void setLastCkp(Lsn lastCkp) {
this.lastCkp.setFileId(lastCkp.getFileId());
this.lastCkp.setOffset(lastCkp.getOffset());
try {
DBState.getInstance().setLatestCheckPoint(lastCkp);
} catch (Exception e) {
logger.error(e);
}
}
/**
* Gets the last transaction ID allocated.
*
* @return The last txn id allocated.
*/
public int getLastTxnId() {
return this.lastTxnId;
}
/**
* Allocates the last transaction ID and saves it to persistent storage.
*
* @param lastTxnId The last txn id.
*/
public void setLastTxnId(int lastTxnId) {
this.lastTxnId = lastTxnId;
try {
saveEnvironment();
} catch (ChaiDBException e) {
logger.error(e);
}
}
/**
* Sets the time of the last completed checkpoint finished.
*
* @param timeCkp The time of the last completed checkpoint finished.
*/
public void setTimeCkp(long timeCkp) {
this.timeCkp = timeCkp;
}
/**
* Gets the time of the last completed checkpoint finished.
*
* @return The time of the last completed checkpoint finished.
*/
public long getTimeCkp() {
return this.timeCkp;
}
/*
* Load environment.
*/
private synchronized void loadEnvironment() {
// byte[] stateValue = new byte[4];
try {
lastTxnId = DBState.getInstance().getLatestTxnId();
} catch (Exception e) {
lastTxnId = TXN_MINIMUM;
}
}
/**
* Close the checkpoint thread.
*/
public void closeCheckpoint() {
CheckpointThread ckpThread = (CheckpointThread) DaemonThreadManager.getInstance().getThread(CheckpointThread.CHECKPOINT_THREADNAME);
if (ckpThread != null) {
ckpThread.setFinish();
while (isCheckpointAlive()) {
}
ckpThread = null;
logger.info("Checkpoint thread is closed.");
}
}
public void doCheckpoint() throws ChaiDBException {
doCheckpoint(false);
}
public void doCheckpoint(boolean sync) throws ChaiDBException {
if (Config.TXN_SUPPORT) {
closeCheckpoint();
if (ckpObj == null) {
ckpObj = new Checkpoint(this, TransactionManager.DB_FORCE);
}
try {
logger.info("Invoking a checkpoint manually.");
ckpObj.doCheckpoint(sync);
} catch (ChaiDBException e) {
logger.error(e);
throw e;
} finally {
DaemonThreadManager.getInstance().shutdown(CheckpointThread.CHECKPOINT_THREADNAME);
}
}
}
/*
* Save environment.
*/
private synchronized void saveEnvironment() throws ChaiDBException {
try {
DBState.getInstance().setLatestTxnId(lastTxnId);
} catch (Exception e) {
logger.error(e);
throw new ChaiDBException(ErrorCode.TXN_ENV_SAVE_FAILED, e.toString());
}
}
/**
* dump buffer information to String as XML format
*
* @return String
*/
public String dump() {
StringBuffer buf = new StringBuffer("<TransactionManagerDump>");
buf.append("<ActiveTxns>" + this.nActiveTxns + "</ActiveTxns>");
buf.append("<LastCheckpoint>" + "<FileId>" + this.lastCkp.getFileId() + "</FileId>" + "<Offset>" + this.lastCkp.getOffset() + "</Offset></LastCheckpoint>");
buf.append("<LastCheckpointTime>" + this.timeCkp + "</LastCheckpointTime>");
buf.append("<LastTxnId>" + Integer.toHexString(this.lastTxnId) + "(" + lastTxnId + ")" + "</LastTxnId>");
Enumeration txns = txnChain.elements();
if (txns == null) {
Hashtable tmp = new Hashtable();
txns = tmp.keys();
}
while (txns.hasMoreElements()) {
TransactionImpl txnImpl = (TransactionImpl) txns.nextElement();
if (txnImpl != null) buf.append(txnImpl.dump());
}
buf.append("</TransactionManagerDump>");
return buf.toString();
}
/**
* get smallest Lsn of all active txn begin_lsn
*/
public Lsn getSmallestLsnOfAllActiveTxn() {
if ((txnChain == null) || (txnChain.isEmpty())) return null;
Lsn tempLsn = null;
Enumeration txnEnum = txnChain.elements();
Lsn txnBeginLsn = null;
TransactionImpl tempTxn;
while (txnEnum.hasMoreElements()) {
tempTxn = (TransactionImpl) txnEnum.nextElement();
txnBeginLsn = tempTxn.getBeginLsn();
/* modified by marriane 2002-6-18 ignore transactions which just
began and no log records during these txns,so its beginLsn is
equals to (-1,-1). */
if (txnBeginLsn.compare(new Lsn(LogManager.INVALID_LSN_FILE_ID, LogManager.INVALID_LSN_OFFSET)) == 0) {
continue;
}
/*modified by marriane 2002-6-20,new Lsn, for not using the beginLsn
reference,because it'll be set to (-1,-1) while txn end. Or it
will cause recover couldn't find log file because the log file
has been moved by archive,but the second-to-last checkpoint's
smallest lsn is (-1,-1),and it leads to recover doing to
(-1,-1)*/
if (tempLsn == null) {
tempLsn = new Lsn(txnBeginLsn);
} else if (tempLsn.compare(txnBeginLsn) > 0) {
/* tempLsn is larger than txnBeginLsn */
tempLsn = new Lsn(txnBeginLsn);//new Lsn
}
}
return tempLsn;
}
public void decActiveTxns() {
if (nActiveTxns > 0) nActiveTxns--;
}
}