/*
* 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.KernelContext;
import org.chaidb.db.exception.ChaiDBException;
import org.chaidb.db.exception.ErrorCode;
import org.chaidb.db.helper.FileUtil;
import org.chaidb.db.helper.MailUtil;
import org.chaidb.db.index.btree.bufmgr.PageBufferManager;
import org.chaidb.db.log.LogManager;
import org.chaidb.db.log.LogRecord;
import org.chaidb.db.log.Lsn;
import org.chaidb.db.log.logrecord.FilesDeleteLogRecord;
import org.chaidb.db.log.logrecord.TxnChildLogRecord;
import org.chaidb.db.log.logrecord.TxnRegopLogRecord;
import java.io.File;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
public class TransactionImpl implements Transaction {
private static final Logger logger = Logger.getLogger(TransactionImpl.class);
private static PageBufferManager bpm = PageBufferManager.getInstance();
/* Unique transaction id. */
private int txnId;
/*
* @supplierCardinality 1
* @clientCardinality *
* @supplierRole manager
*/
private TransactionManager lnkTransactionManager;
/* Pointer to transaction's parent. */
private TransactionImpl parent;
/* Child transactions. */
private Hashtable childs;
/* Flags of the transaction */
private int flags;
/* Status of the transaction */
private int status;
/* Last lsn written for this txn. */
private Lsn lastLsn;
/*
* @associates <{org.chaidb.db.log.Lsn}>
*/
private Lsn beginLsn;
/* The kernel context associating with this transaction. */
private KernelContext kCtx;
/* container for saving delete collection/index absolute path
It's used for killing inconsistent issue in DDL operation.
*/
private Vector deleteFiles = new Vector();
/**
* A abortHook to be performed when abort
*/
private AbortHook abortHook;
/**
* Gets the last lsn of this transaction.
*
* @return The last lsn of this transaction.
*/
public Lsn getLastLsn() {
return lastLsn;
}
/**
* Sets the last lsn of this transaction.
*
* @param lastLsn The last lsn of this transaction should be set.
*/
public void setLastLsn(Lsn lastLsn) {
this.lastLsn.setFileId(lastLsn.getFileId());
this.lastLsn.setOffset(lastLsn.getOffset());
}
/**
* Sets the begin lsn of the transaction.
*
* @param beginLsn The begin lsn of this transaction should be set.
*/
public void setBeginLsn(Lsn beginLsn) {
this.beginLsn.setFileId(beginLsn.getFileId());
this.beginLsn.setOffset(beginLsn.getOffset());
}
/**
* Gets the begin lsn of this transaction.
*
* @return The begin lsn of this transaction.
*/
public Lsn getBeginLsn() {
return this.beginLsn;
}
/**
* The constructor.
*
* @param txnManager The transaction manager is responsible for managing the transactions.
*/
public TransactionImpl(TransactionManager txnManager) {
lnkTransactionManager = txnManager;
childs = new Hashtable();
//initialize beginLsn ans lastLsn.
beginLsn = new Lsn(LogManager.INVALID_LSN_FILE_ID, LogManager.INVALID_LSN_OFFSET);
lastLsn = new Lsn(LogManager.INVALID_LSN_FILE_ID, LogManager.INVALID_LSN_OFFSET);
this.flags = 0;
this.status = 0;
}
/**
* Gets the transaction's parent.
*
* @return The transaction's parent.
*/
public TransactionImpl getParent() {
return parent;
}
/**
* Sets the transaction's parent.
*
* @param parent transaction's parent.
*/
public void setParent(TransactionImpl parent) {
this.parent = parent;
}
/**
* Gets the transaction manager
*/
public TransactionManager getTxnManager() {
return this.lnkTransactionManager;
}
/**
* Gets the unique transaction id hold by this transaction.
*
* @return The unique transaction id.
*/
public int getTxnId() {
return this.txnId;
}
/**
* Sets the unique transaction id hold by this transaction.
*
* @param txnId The unique transaction id.
*/
public void setTxnId(int txnId) {
this.txnId = txnId;
}
/**
* Gets the child transactions belong to the transaction.
*
* @return The child transactions belong to the transaction.
*/
public Hashtable getChilds() {
return childs;
}
/**
* Adds child transaction to parent transaction, viz. itself.
*
* @param child The child transaction.
*/
public void addChild(Transaction child) {
this.childs.put(new Integer(child.getTxnId()), child);
}
/**
* Gets the flags of the transaction.
*
* @return The flags of the transaction.
* The flags parameter can be one of the below values.
* Transaction.TXN_SYNC or Transaction.TXN_NOSYNC or Transaction.TXN_NOWAIT
*/
public int getFlags() {
return flags;
}
/**
* Set the flags of the transaction.
*
* @param flags The flags can be one of the below values
* Transaction.TXN_SYNC or Transaction.TXN_NOSYNC or Transaction.TXN_NOWAIT
*/
public void setFlags(int flags) {
this.flags |= flags;
}
/**
* Gets the status of the transaction.
*
* @return The status of the transaction.
* The status of the transaction can be one of the below values
* Transaction.TXN_RUNNING or Transaction.TXN_ABORTING or Transaction.TXN_COMMITING
*/
public int getStatus() {
return status;
}
/**
* Sets the status of the transaction.
*
* @param status The status of the transaction, it can be the following value:
* Transaction.TXN_RUNNING or Transaction.TXN_ABORTING or Transaction.TXN_COMMITING
*/
public void setStatus(int status) {
this.status = status;
}
public void addAbortHook(AbortHook hook) {
if (this.abortHook != null) {
hook.setNextHook(this.abortHook);
}
this.abortHook = hook;
}
public AbortHook getAbortHook() {
return abortHook;
}
/**
* Abort this transaction.
*/
public void abort() throws ChaiDBException {
Enumeration enumChilds;
TransactionImpl child;
switch (isTxnValid(TXN_ABORTING)) {
case 0:
break;
case 1:
throw new ChaiDBException(ErrorCode.IN_RECOVER_PROCESS);
case 2:
throw new ChaiDBException(ErrorCode.RECOMMITED_FAILURE);
case 3:
throw new ChaiDBException(ErrorCode.REABORTTED_FAILURE);
case 4:
break;
case 5:
break;
}
if (abortHook != null) {
abortHook.beforeAbort();
}
this.status = TXN_ABORTING;
/* Abort any unresolved children. */
enumChilds = this.childs.elements();
while (enumChilds.hasMoreElements()) {
child = (TransactionImpl) enumChilds.nextElement();
try {
child.abort();
} catch (ChaiDBException e) {
logger.error(e);
throw e;
}
}
try {
undo();
lnkTransactionManager.getMemLogManager().rollback(txnId);
} catch (ChaiDBException e) {
logger.fatal(e);
String body = "Dear user,Transaction undo failed. The server will shut down. Please reboot the server.";
MailUtil.notify(body);
System.exit(-1);
} catch (Exception e1) {
logger.fatal(e1);
String body = "Dear user,Transaction undo failed. The server will shut down. Please reboot the server.";
MailUtil.notify(body);
System.exit(-1);
} finally {
this.status = Transaction.TXN_RUNNING;
if (abortHook != null) {
abortHook.afterAbort();
}
//Move it into finally to guarantee to release resource and put locks
this.endTxn(false);
}
}
/**
* Commit this transaction.
*/
public void commit() throws ChaiDBException {
boolean isCommit = true;
Transaction child, child1;
switch (isTxnValid(TXN_COMMITING)) {
case 0:
break;
case 1:
throw new ChaiDBException(ErrorCode.IN_RECOVER_PROCESS);
case 2:
throw new ChaiDBException(ErrorCode.RECOMMITED_FAILURE);
case 3:
throw new ChaiDBException(ErrorCode.REABORTTED_FAILURE);
case 4:
break;
case 5:
break;
}
//Set status to committed
status = TXN_COMMITING;
/*
* Commit any unresolved children. If there's an error, abort any
* unresolved children and the parent.
*/
Enumeration childTxns = this.getChilds().elements();
while (childTxns.hasMoreElements()) {
child = (Transaction) childTxns.nextElement();
try {
child.commit();
} catch (ChaiDBException txnAbortedEx) { //If one child txn isn't committed successfully, all childs txns will be aborted.
logger.error(txnAbortedEx);
Enumeration childTxns1 = this.getChilds().elements();
while (childTxns1.hasMoreElements()) {
child1 = (Transaction) childTxns1.nextElement();
try {
child1.abort();
} catch (ChaiDBException e) {
logger.error(e);
// preserving all the previous abort reports -ranjeet
ChaiDBException highLevel = new ChaiDBException(ErrorCode.ABORT_FAILURE, txnAbortedEx);
throw highLevel;
}
}//end while
try {
this.abort(); // Abort itself parent.
} catch (ChaiDBException e) {
logger.error(e);
// preserving all the previous abort reports -ranjeet
ChaiDBException highLevel = new ChaiDBException(ErrorCode.ABORT_FAILURE, txnAbortedEx);
throw highLevel;
}
throw new ChaiDBException(ErrorCode.COMMIT_FAILURE, txnAbortedEx);
}//end try
}//end while
try {
//At first we precommit the memory log
lnkTransactionManager.getMemLogManager().preCommit(txnId);
/*
* If there are any log records, write a log record and sync the log,
* else do no log writes. If the commit is for a child transaction,
* we do not need to commit the child synchronously since it may still
* abort (if its parent aborts), and otherwise its parent or ultimate
* ancestor will write synchronously.
*
* I'd rather return a logging error than a flag-wrong error, so if
* the log routines fail, set "ret" without regard to previous value.
*/
if (!(lastLsn.getFileId() == 0)) {
bpm.flushChangedDocRoot(kCtx);
if (this.parent == null) {
int flag = 0;
if ((this.flags & Transaction.TXN_NOSYNC) != 0) {
flag = Transaction.TXN_NOSYNC;
} else if ((this.flags & Transaction.TXN_SYNC) != 0) {
flag = Transaction.TXN_SYNC;
}
//Comment it out. Maybe it is caused long txn.
//The commit log record has inserted into log file.
//But before it will be removed from active txn list, there
//are some activity need to done, include memlogMgr.commit(),
//release resources, put locks etc. Once one of activity
//throw exception, the txn will never be removed from active txn list.
//I will move it into endTxn.
//(new TxnRegopLogRecord(TXN_COMMIT,(new Date()).getTime(),this.txnId)).log(flag);
} else { /* Log the commit in the parent! */
(new TxnChildLogRecord(txnId, lastLsn, this.parent.getTxnId())).log(); //pending ?!
parent.setFlags(TXN_CHILDCOMMIT);
}
} //end if (!(lastLsn.getFileId() == 0))
lnkTransactionManager.getMemLogManager().commit(txnId);
} catch (ChaiDBException ie) {
logger.error(ie);
throw ie;
} catch (Exception excp) {
logger.error(excp);
throw new ChaiDBException(ErrorCode.TXN_ERROR_BASE, excp);
} finally {
status = Transaction.TXN_RUNNING;
}
endTxn(isCommit);
}
/*
* Judge whether the transaction is valid.
* @param op The op parameter MUST be set one of the following values at present.
* Transaction.TXN_OP_ABORT or Transaction.TXN_OP_COMMIT
* @return 0 if the transaction is reasonable, otherwise panic.
* The following return value is available:
* 0 The transaction is valid, it is available to do other operation.
* 1 The transaction manager is doing recovery process.
* 2 The transaction is doing commit process,
* 3 The transaction is doing abort process.
*
*/
private int isTxnValid(int op) {
int rtn = 0;
/* Check for recovery. */
if ((lnkTransactionManager.getFlags() & TransactionManager.TXN_IN_RECOVERY) != 0) rtn = 1;
/* Check transaction's status. */
switch (status) {
case TXN_COMMITING:
rtn = 2;
break;
case TXN_ABORTING:
rtn = 3;
break;
default:
rtn = 4;
break;
case TXN_PREPARED:
if (op == TXN_OP_PREPARE) {
rtn = 5;
break;
}
break;
case TXN_RUNNING:
break;
}
return rtn;
}
/*
* Internal transaction end routine called by commit or abort method.
*/
private void endTxn(boolean isCommit) throws ChaiDBException {
/* begin: added by marriane 2001-12-20 release all btree resource of this txn */
bpm.releaseResource(txnId);
/* end: added by marriane 2001-12-20 release all btree resource of this txn */
/* Release the locks owned by this transaction. */
lnkTransactionManager.getLockManager().putAll(txnId);
/* Updates the commit/abort state in transaction manager. */
if (isCommit) lnkTransactionManager.setCommittedTxns(lnkTransactionManager.getCommittedTxns() + 1);
else lnkTransactionManager.setAbortedTxns(lnkTransactionManager.getAbortedTxns() + 1);
/* Updates the active state in transaction manager. */
if (lnkTransactionManager.getActiveTxns() > 0) {
lnkTransactionManager.decActiveTxns();
}
/* Remove it from parent. */
if (parent != null) parent.getChilds().remove(new Integer(txnId));
//To guarantee add one commit log record before removing txn from active txn list
if (isCommit) {
deleteAndLoggingDeletedFiles();
(new TxnRegopLogRecord(TXN_COMMIT, (new Date()).getTime(), this.txnId)).log(TXN_SYNC);
}
/* Remove this transaction from transaction chain in the transaction manager. */
lnkTransactionManager.getTxnChain().remove(new Integer(txnId));
purge();
/* Return the object to free pool. */
lnkTransactionManager.getFreePool().insert(this);
}
private static final Lsn _INVALID_LSN = new Lsn(LogManager.INVALID_LSN_FILE_ID, LogManager.INVALID_LSN_OFFSET);
/**
* Undo the transaction.
*/
private void undo() throws ChaiDBException {
Lsn keyLsn;
LogRecord logRecord;
keyLsn = this.lastLsn;
if (keyLsn.equals(_INVALID_LSN)) return;
logRecord = lnkTransactionManager.getLogManager().get(keyLsn);
/* Allocate a transaction list for children or aborted page creates. */
for (keyLsn = this.lastLsn; !keyLsn.equals(_INVALID_LSN); keyLsn = logRecord.getPrevLsn()) {
logRecord = lnkTransactionManager.getLogManager().get(keyLsn);
try {
logRecord.recover(LogRecord.UNDO);
} catch (ChaiDBException ie) {
logger.error(ie);
throw new ChaiDBException(ErrorCode.UNDO_FAILURE, ie);
}
}//End for
}
/*
* Purge the instance
*/
void purge() {
flags = 0;
status = 0;
txnId = TransactionManager.TXN_INVALID_ID;
lastLsn.setFileId(LogManager.INVALID_LSN_FILE_ID);
lastLsn.setOffset(LogManager.INVALID_LSN_OFFSET);
beginLsn.setFileId(LogManager.INVALID_LSN_FILE_ID);
beginLsn.setOffset(LogManager.INVALID_LSN_OFFSET);
//lnkTransactionManager = null;
parent = null;
childs.clear();
deleteFiles.clear();
abortHook = null;
}
/**
* Sets the kernel context associating with this transaction.
*
* @param kCtx The kernel context associating with this transaction.
*/
public void setKernelContext(KernelContext kCtx) {
this.kCtx = kCtx;
}
/**
* Gets the kernel context associating with this transaction.
*
* @return The kernel context associating with this transaction.
*/
public KernelContext getKernelContext() {
return this.kCtx;
}
/**
* Dumps the status information of the active transaction to String as XML format.
*
* @return The status information of the active transaction.
*/
public String dump() {
StringBuffer buf = new StringBuffer("<Transaction>");
buf.append("<TxnId>" + Integer.toHexString(this.txnId) + "(" + txnId + ")" + "</TxnId>");
String tmpStatus = null;
switch (this.status) {
case Transaction.TXN_RUNNING:
tmpStatus = "TXN_RUNNING";
break;
case Transaction.TXN_ABORTING:
tmpStatus = "TXN_ABORTING";
break;
case Transaction.TXN_COMMITING:
tmpStatus = "TXN_COMMITING";
break;
default:
tmpStatus = "UNKNOWN_STATUS";
}
buf.append("<TxnStatus>" + tmpStatus + "</TxnStatus>");
buf.append("</Transaction>");
return buf.toString();
}
/**
* If a DDL operation is going to be done, it adds an event of deleting
* file or directories in transaction impl,and all datalog logging and
* folder or file deletion would be done during transaction committed.
*
* @param filePath
*/
public void addFileDeleteOperationEvent(String filePath) {
deleteFiles.add(filePath);
}
/**
* files or folders deleted during DDL transaction committed.
*/
private void deleteAndLoggingDeletedFiles() throws ChaiDBException {
int deleteFilesNumber = deleteFiles.size();
if (deleteFilesNumber <= 0) {
return;
}
FilesDeleteLogRecord logRec = new FilesDeleteLogRecord(deleteFiles, txnId);
logRec.log();
for (int i = 0; i < deleteFilesNumber; i++) {
String filePath = (String) deleteFiles.get(i);
File deleteFile = new File(filePath);
if (deleteFile.isFile()) {
FileUtil.removeFileOrDirectory(deleteFile);
} else {
try {
bpm.dCloseAllTreesofCollection(deleteFile);
} catch (Exception e) {
logger.error(e);
} finally {
FileUtil.removeFileOrDirectory(deleteFile);
}
}
}
}
}