/*
* 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.recover;
import org.apache.log4j.Logger;
import org.chaidb.db.Db;
import org.chaidb.db.DbEnvironment;
import org.chaidb.db.exception.ChaiDBException;
import org.chaidb.db.exception.ErrorCode;
import org.chaidb.db.helper.Config;
import org.chaidb.db.helper.FileUtil;
import org.chaidb.db.index.btree.bufmgr.PageBufferManager;
import org.chaidb.db.log.LogRecord;
import org.chaidb.db.log.Lsn;
import org.chaidb.db.transaction.TransactionManager;
import java.io.*;
import java.util.HashMap;
import java.util.Stack;
public abstract class TransactionRecoverImpl implements TransactionRecover {
private static PageBufferManager bpm = PageBufferManager.getInstance();
protected static final String RECOVERY_TMP = DbEnvironment.DB_TEMP_PATH + File.separator + RECOVERY_TEMP_DIR;
protected TransactionManager txnManager;
protected HashMap redoTable;
protected Stack redoStack, lsnStack;
protected int stacks;
/**
* server log
*/
private static final Logger logger = Logger.getLogger(TransactionRecoverImpl.class);
/**
* the max size of stack here, such as redoStack and lsnStack.
*/
protected static final int STACK_MAXSIZE = 50000;
/**
* The flag to indicate on which stage is stay of the recovery.
*/
protected int status = INVALID_STAGE;
/**
* The length of log records to redo.
*/
protected int redo_length = 0;
/**
* The length of log records divide five.
*/
protected int redo_average = 0;
/**
* Current indicator which log record is doing in redo stage.
*/
protected int redo_current = 0;
//private static TransactionRecover txnRecover = null;
public TransactionRecoverImpl(TransactionManager txnManager) {
this.txnManager = txnManager;
//this.txnManager.setRecover(this);
redoTable = new HashMap();
redoStack = new Stack();
stacks = 0;
}
/**
* Do recover.
* recover flow: undo->release all btree resource touched by undo->
* redo(release all resource touched by current txn)->close all btrees
* ->insert a DB_FORCE checkpoint
*/
public boolean doRecover() throws ChaiDBException {
// Sets flags to recover, preventing someone from beginTxn during recover routine.
Db.getTxnManager().setFlags(TransactionManager.TXN_IN_RECOVERY);
try {
long start = System.currentTimeMillis();
File dir = new File(RECOVERY_TMP);
if (dir.exists() && dir.isDirectory()) {
FileUtil.removeFileOrDirectory(dir);
}
////////////////////////////// UNDO OPERATION /////////////////////////////////
try {
if (!undo()) return false;
} catch (Exception e) {
this.status = INVALID_STAGE;
logger.error(e);
releaseBtrees();//close all btrees, release all btree resource of all abort txn
throw new ChaiDBException(ErrorCode.RECOVER_ERROR_BASE, e.toString());
} finally {
bpm.releaseAllTxnResources();
}
///////////////////////// REDO OPERATION //////////////////////////////////////////
try {
redo();
} catch (Exception e) {
this.status = INVALID_STAGE;
logger.error(e);
throw new ChaiDBException(ErrorCode.RECOVER_ERROR_BASE, e.toString());
} finally {
releaseBtrees();//close all btrees
}
this.status = INVALID_STAGE;
txnManager.doCheckpoint(true);
logger.info("Total time is " + (System.currentTimeMillis() - start) + "ms");
//Clear up routine.
redoTable.clear();
redoStack.clear();
} catch (ChaiDBException ie) {
throw ie;
} catch (Exception e) {
logger.error(e);
throw new ChaiDBException(ErrorCode.RECOVER_ERROR_BASE, e.toString());
} finally {
// Remove TXN_IN_RECOVERY flag from txn manager.
txnManager.clearFlags(TransactionManager.TXN_IN_RECOVERY);
}
return true;
}
/*
* Undo operation.
* Implements by subclasses.
*/
protected abstract boolean undo() throws ChaiDBException;
/*
* Redo operation.
* @exception ChaiDBException Exception while doing redo operation.
*
*/
private void redo() throws ChaiDBException {
LogRecord cursorLogRecord;
boolean cont = true;
int currentProcess = 0;
int percent;
while (cont) {
long start1 = System.currentTimeMillis();
while (redoStack.size() != 0) {
cursorLogRecord = (LogRecord) redoStack.pop();
if (this instanceof CatastrophicTxnRecoverImpl) {
redo_current++;
percent = redo_current / redo_average;
if (percent != currentProcess) {
currentProcess++;
if (currentProcess <= 5) System.out.print("\r" + (currentProcess + 5) + "0% completed.");
}
}
try {
cursorLogRecord.recover(LogRecord.REDO);
} catch (ChaiDBException e) {
logger.error(e.toString());
continue;
}
}
if (cont = getNextStack()) {
logger.info("Change Stack, remains " + (--stacks) + " cost " + (System.currentTimeMillis() - start1) + "ms");
}
}
if (this instanceof CatastrophicTxnRecoverImpl) {
if (redo_length == 0) {
for (int i = 6; i <= 10; i++) {
System.out.print("\r" + i + "0% completed.");
}
}
System.out.println();
}
}
protected String colon2sub(String olds) {
return olds.replace(':', '-');
}
protected void saveStack(Lsn lsn) {
ObjectOutputStream out = null;
try {
File dir = new File(RECOVERY_TMP);
if (!dir.exists() || !dir.isDirectory()) dir.mkdirs();
out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(RECOVERY_TMP + File.separator + "redo_" + colon2sub(lsn.toString()))));
out.writeObject(redoStack);
out.flush();
out.close();
} catch (IOException e) {
//delete all file in temp dir
File dir = new File(RECOVERY_TMP);
File[] fileList = dir.listFiles();
for (int i = 0; i < fileList.length; i++) {
File tempFile = fileList[i];
tempFile.delete();
}
logger.fatal(e);
if (out != null) {
try {
out.close();
} catch (IOException ee) {
logger.error(ee);
}
}
logger.fatal("Failed in saveStack of recovery process: lsn = " + lsn.toString() + ". System will in inconsistant state");
logger.fatal("recover failed because disk is full ");
System.exit(-1);
}
redoStack = new Stack();
}
protected boolean getNextStack() {
File dir = new File(RECOVERY_TMP);
String[] fname;
String tmp0, tmp1, tmp2;
Lsn lsn = new Lsn();
Lsn minLsn = new Lsn(Short.MAX_VALUE, Integer.MAX_VALUE);
if (dir.exists() && dir.isDirectory()) {
fname = dir.list();
for (int i = 0; i < fname.length; i++)
if (fname[i].startsWith("redo_")) {
tmp0 = fname[i].substring(5);
int index = tmp0.indexOf("-");
if (index > -1) {
tmp1 = tmp0.substring(0, index);
tmp2 = tmp0.substring(index + 1);
lsn.setFileId(Integer.parseInt(tmp1));
lsn.setOffset(Integer.parseInt(tmp2));
if (lsn.compare(minLsn) == -1) {
minLsn.setFileId(lsn.getFileId());
minLsn.setOffset(lsn.getOffset());
}
}
}//end for
if (fname.length == 0) return false;
File f0;
ObjectInputStream in = null;
try {
f0 = new File(RECOVERY_TMP + File.separator + "redo_" + colon2sub(minLsn.toString()));
in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(f0)));
redoStack = (Stack) in.readObject();
in.close();
return f0.delete();
} catch (Exception e) {
logger.error(e);
if (in != null) try {
in.close();
} catch (IOException ee) {
logger.error(ee);
}
}
}
return false;
}
/*
* Releases the opened btrees.
*/
private void releaseBtrees() {
/* close all btrees */
bpm.closeAllBTrees();
}
}