/*
* 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.
*
*/
package org.chaidb.db.transaction.recover;
import org.chaidb.db.exception.ChaiDBException;
import org.chaidb.db.exception.ErrorCode;
import org.chaidb.db.helper.FileUtil;
import org.chaidb.db.log.Lsn;
import java.io.*;
import java.util.LinkedList;
/**
* @author Kurt Sung
*/
public class TxnQueue {
/**
* The maximum log buffer is 2M, this is for supporting 100 concurrent
* transactions in 256M heap size. This will be configurable in the feature.
*/
private static final int MAX_LOG_BUFFER_SIZE = 2 * 1024 * 1024; //2M
/**
* The maximum temp file size is 1G, this is for supporting FAT partition.
* This will be configurable in the feature.
*/
private static final long MAX_FILE_SIZE = 1000 * 1024 * 1024; //1G
/**
* transaction id of this queue
*/
private final Integer txnId;
/**
* in outMode, just support poll method
*/
private boolean outMode;
/**
* Buffer
*/
private LinkedList list = new LinkedList();
private int bufferSize;
private int size = 0;
/**
* Swap file
*/
private DataOutputStream out;
private DataInputStream in;
private int wfileId;
private int rfileId;
private long length; //length of current file
/**
* Attributes
*/
private final String path;
private final String filename; //name of current file
private final Lsn firstLsn;
public TxnQueue(Integer txnId, Lsn firstLsn, String path) {
this.txnId = txnId;
this.firstLsn = new Lsn(firstLsn);
this.path = path;
filename = path + File.separator + "txn_" + Integer.toHexString(txnId.intValue());
}
public Integer getTxnId() {
return txnId;
}
public Lsn getFirstLsn() {
return firstLsn;
}
/**
* Get the count of all log records in this queue
*
* @return the count of log records
*/
public int getSize() {
return size;
}
/**
* Offer the data of a record to the queue
*
* @param data
* @throws ChaiDBException
*/
public void offer(byte[] data) throws ChaiDBException {
if (outMode) {
System.err.println("Offer in read mode");
return;
}
bufferSize += data.length;
list.add(data);
size++;
if (bufferSize > MAX_LOG_BUFFER_SIZE) {
try {
flush();
} catch (IOException e) {
System.err.println("offer error");
throw new ChaiDBException(ErrorCode.RECOVER_ERROR_BASE, e);
}
}
}
/**
* Poll the data of next log record from the queue
*
* @return
* @throws ChaiDBException
*/
public byte[] poll() throws ChaiDBException {
if (!outMode) {
if (out != null) {
pack();
}
outMode = true;
}
if (list.size() == 0) {
try {
load();
} catch (IOException e) {
System.err.println("poll error");
throw new ChaiDBException(ErrorCode.RECOVER_ERROR_BASE, e);
}
}
if (list.size() != 0) {
size--;
return (byte[]) list.remove(0);
}
return null;
}
private void load() throws IOException {
if (in == null) {
if (out != null) {
out.close();
out = null;
}
final File file = new File(getFullName(rfileId));
if (!file.exists()) {
return;
}
length = file.length();
in = new DataInputStream(new BufferedInputStream(new FileInputStream(getFullName(rfileId))));
}
bufferSize = 0;
while ((bufferSize < MAX_LOG_BUFFER_SIZE) && (length > 0)) {
short len = in.readShort();
byte[] data = new byte[len];
in.read(data);
list.add(data);
bufferSize += data.length;
length -= (2 + len);
if (length == 0) {
in.close();
new File(getFullName(rfileId)).delete();
in = null;
if (rfileId < wfileId) {
rfileId++;
in = new DataInputStream(new BufferedInputStream(new FileInputStream(getFullName(rfileId))));
length = new File(getFullName(rfileId)).length();
}
}
}
}
private void flush() throws IOException {
File dir = new File(path);
if (out == null) {
if (!dir.exists() || !dir.isDirectory()) {
dir.mkdirs();
}
File f = new File(getFullName(wfileId));
if (f.exists()) {
FileUtil.removeFileOrDirectory(f);
}
out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(getFullName(wfileId))));
}
int size = list.size();
for (int i = 0; i < size; i++) {
byte[] data = (byte[]) list.removeFirst();
out.writeShort(data.length);
out.write(data);
length += (2 + data.length);
}
out.flush();
list.clear();
bufferSize = 0;
if (length > MAX_FILE_SIZE) {
out.close();
wfileId++;
File f = new File(getFullName(wfileId));
if (f.exists()) {
FileUtil.removeFileOrDirectory(f);
}
out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(getFullName(wfileId))));
length = 0;
}
}
private void pack() throws ChaiDBException {
try {
flush();
out.close();
} catch (IOException e) {
System.err.println("pack error");
throw new ChaiDBException(ErrorCode.RECOVER_ERROR_BASE, e);
}
}
public void abandon() {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
;
}
list.clear();
size = 0;
bufferSize = 0;
wfileId = 0;
rfileId = 0;
for (int i = 0; i < wfileId; i++) {
new File(getFullName(i)).delete();
}
}
private String getFullName(int id) {
return filename + "." + id;
}
}