/*******************************************************************************
- * Copyright (c) 2012, 2013 Ericsson
+ * Copyright (c) 2012, 2014 Ericsson
* Copyright (c) 2010, 2011 École Polytechnique de Montréal
* Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
*
import java.nio.channels.FileChannel;
/**
- * This class exists mainly for code isolation/clarification purposes. It
- * contains all the methods and descriptors to handle reading/writing to the
- * tree-file on disk and all the caching mechanisms. Every HistoryTree should
- * contain 1 and only 1 HT_IO element.
+ * This class abstracts inputs/outputs of the HistoryTree nodes.
*
- * @author alexmont
+ * It contains all the methods and descriptors to handle reading/writing nodes
+ * to the tree-file on disk and all the caching mechanisms.
+ *
+ * This abstraction is mainly for code isolation/clarification purposes.
+ * Every HistoryTree must contain 1 and only 1 HT_IO element.
+ *
+ * @author Alexandre Montplaisir
*
*/
class HT_IO {
-
- /* reference to the tree to which this IO-object belongs */
- private final HistoryTree tree;
+ /* Configuration of the History Tree */
+ private final HTConfig fConfig;
/* Fields related to the file I/O */
- private final File historyTreeFile;
private final FileInputStream fis;
private final FileOutputStream fos;
private final FileChannel fcIn;
private final FileChannel fcOut;
+ // TODO test/benchmark optimal cache size
+ private final int CACHE_SIZE = 256;
+ private final HTNode fNodeCache[] = new HTNode[CACHE_SIZE];
+
/**
* Standard constructor
*
- * @param tree
+ * @param config
+ * The configuration object for the StateHistoryTree
* @param newFile
- * Are we creating a new file from scratch?
+ * Flag indicating that the file must be created from scratch
+
* @throws IOException
+ * An exception can be thrown when file cannot be accessed
*/
- HT_IO(HistoryTree tree, boolean newFile) throws IOException {
- this.tree = tree;
- historyTreeFile = tree.config.stateFile;
- boolean success1 = true, success2;
+ public HT_IO(HTConfig config, boolean newFile) throws IOException {
+ fConfig = config;
+ File historyTreeFile = config.getStateFile();
if (newFile) {
+ boolean success1 = true;
/* Create a new empty History Tree file */
if (historyTreeFile.exists()) {
success1 = historyTreeFile.delete();
}
- success2 = historyTreeFile.createNewFile();
+ boolean success2 = historyTreeFile.createNewFile();
if (!(success1 && success2)) {
/* It seems we do not have permission to create the new file */
throw new IOException("Cannot create new file at " + //$NON-NLS-1$
}
/**
- * Generic "read node" method, which checks if the node is in memory first,
- * and if it's not it goes to disk to retrieve it.
+ * Read a node from the file on disk.
*
* @param seqNumber
- * Sequence number of the node we want
- * @return The wanted node in object form
- * @throws ClosedChannelException
- * If the channel was closed before we could read
- */
- HTNode readNode(int seqNumber) throws ClosedChannelException {
- HTNode node = readNodeFromMemory(seqNumber);
- if (node == null) {
- return readNodeFromDisk(seqNumber);
- }
- return node;
- }
-
- private HTNode readNodeFromMemory(int seqNumber) {
- for (HTNode node : tree.latestBranch) {
- if (node.getSequenceNumber() == seqNumber) {
- return node;
- }
- }
- return null;
- }
-
- /**
- * This method here isn't private, if we know for sure the node cannot be in
- * memory it's a bit faster to use this directly (when opening a file from
- * disk for example)
- *
+ * The sequence number of the node to read.
+ * @return The object representing the node
* @throws ClosedChannelException
* Usually happens because the file was closed while we were
* reading. Instead of using a big reader-writer lock, we'll
* just catch this exception.
*/
- synchronized HTNode readNodeFromDisk(int seqNumber) throws ClosedChannelException {
- HTNode readNode;
+ public synchronized HTNode readNode(int seqNumber) throws ClosedChannelException {
+ /* Do a cache lookup */
+ int offset = seqNumber & (CACHE_SIZE - 1);
+ HTNode readNode = fNodeCache[offset];
+ if (readNode != null && readNode.getSequenceNumber() == seqNumber) {
+ return readNode;
+ }
+
+ /* Lookup on disk */
try {
seekFCToNodePos(fcIn, seqNumber);
- readNode = HTNode.readNode(tree, fcIn);
+ readNode = HTNode.readNode(fConfig, fcIn);
+
+ /* Put the node in the cache. */
+ fNodeCache[offset] = readNode;
return readNode;
} catch (ClosedChannelException e) {
throw e;
}
}
- void writeNode(HTNode node) {
+ public synchronized void writeNode(HTNode node) {
try {
+ /* Insert the node into the cache. */
+ int seqNumber = node.getSequenceNumber();
+ int offset = seqNumber & (CACHE_SIZE - 1);
+ fNodeCache[offset] = node;
+
/* Position ourselves at the start of the node and write it */
- seekFCToNodePos(fcOut, node.getSequenceNumber());
+ seekFCToNodePos(fcOut, seqNumber);
node.writeSelf(fcOut);
} catch (IOException e) {
/* If we were able to open the file, we should be fine now... */
}
}
- FileChannel getFcOut() {
+ public FileChannel getFcOut() {
return this.fcOut;
}
- FileInputStream supplyATReader() {
+ public FileInputStream supplyATReader(int nodeOffset) {
try {
/*
* Position ourselves at the start of the Mapping section in the
* file (which is right after the Blocks)
*/
- seekFCToNodePos(fcIn, tree.getNodeCount());
+ seekFCToNodePos(fcIn, nodeOffset);
} catch (IOException e) {
e.printStackTrace();
}
return fis;
}
- File supplyATWriterFile() {
- return tree.config.stateFile;
- }
-
- long supplyATWriterFilePos() {
- return HistoryTree.getTreeHeaderSize()
- + ((long) tree.getNodeCount() * tree.config.blockSize);
- }
-
- synchronized void closeFile() {
+ public synchronized void closeFile() {
try {
fis.close();
fos.close();
}
}
- synchronized void deleteFile() {
+ public synchronized void deleteFile() {
closeFile();
- if(!historyTreeFile.delete()) {
+ File historyTreeFile = fConfig.getStateFile();
+ if (!historyTreeFile.delete()) {
/* We didn't succeed in deleting the file */
//TODO log it?
}
* Seek the given FileChannel to the position corresponding to the node that
* has seqNumber
*
- * @param seqNumber
+ * @param fc the channel to seek
+ * @param seqNumber the node sequence number to seek the channel to
* @throws IOException
*/
private void seekFCToNodePos(FileChannel fc, int seqNumber)
throws IOException {
- fc.position(HistoryTree.getTreeHeaderSize() + (long) seqNumber
- * tree.config.blockSize);
/*
- * cast to (long) is needed to make sure the result is a long too and
+ * Cast to (long) is needed to make sure the result is a long too and
* doesn't get truncated
*/
+ fc.position(HistoryTree.TREE_HEADER_SIZE
+ + ((long) seqNumber) * fConfig.getBlockSize());
}
}