tmf: Synchronize accesses to HistoryTree's latest branch
authorAlexandre Montplaisir <alexmonthy@voxpopuli.im>
Tue, 28 Jan 2014 18:38:50 +0000 (13:38 -0500)
committerAlexandre Montplaisir <alexmonthy@voxpopuli.im>
Tue, 11 Feb 2014 22:37:58 +0000 (17:37 -0500)
Most accesses are punctual, so the synchronizedList wrapper covers
for most of them. There is the iteration in readNode() that is done
by reader threads, but it's usually very quick, so there was not
really much advantage of using a ReadWriteLock - considering the
downside of more verbose locking.

Also made the list final and avoided exposing it publicly - the
backend object only needs the root node.

Change-Id: I46c315d46a5e96d609c052d4cedb4404fe765176
Signed-off-by: Alexandre Montplaisir <alexmonthy@voxpopuli.im>
Reviewed-on: https://git.eclipse.org/r/21219
Reviewed-by: Marc-Andre Laperle <marc-andre.laperle@ericsson.com>
IP-Clean: Marc-Andre Laperle <marc-andre.laperle@ericsson.com>
Tested-by: Marc-Andre Laperle <marc-andre.laperle@ericsson.com>
Tested-by: Hudson CI
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/internal/tmf/core/statesystem/backends/historytree/HistoryTree.java
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/internal/tmf/core/statesystem/backends/historytree/HistoryTreeBackend.java

index 15ba244b13034133d8be0dc1e5074b8a13d0f88d..5b7b25d1b791e5abcfa4cd311f99080ab34252c2 100644 (file)
@@ -68,7 +68,7 @@ public class HistoryTree {
     private int nodeCount;
 
     /** "Cache" to keep the active nodes in memory */
-    private List<CoreNode> latestBranch;
+    private final List<CoreNode> latestBranch;
 
     // ------------------------------------------------------------------------
     // Constructors/"Destructors"
@@ -96,7 +96,7 @@ public class HistoryTree {
         config = conf;
         treeEnd = conf.getTreeStart();
         nodeCount = 0;
-        latestBranch = new ArrayList<>();
+        latestBranch = Collections.synchronizedList(new ArrayList<CoreNode>());
 
         /* Prepare the IO object */
         treeIO = new HT_IO(config, true);
@@ -188,19 +188,43 @@ public class HistoryTree {
          */
         this.treeIO = new HT_IO(config, false);
 
-        rebuildLatestBranch(rootNodeSeqNb);
-        this.treeEnd = latestBranch.get(0).getNodeEnd();
+        this.latestBranch = buildLatestBranch(rootNodeSeqNb);
+        this.treeEnd = getRootNode().getNodeEnd();
 
         /*
          * Make sure the history start time we read previously is consistent
          * with was is actually in the root node.
          */
-        if (startTime != latestBranch.get(0).getNodeStart()) {
+        if (startTime != getRootNode().getNodeStart()) {
             throw new IOException("Inconsistent start times in the" + //$NON-NLS-1$
                     "history file, it might be corrupted."); //$NON-NLS-1$
         }
     }
 
+    /**
+     * Rebuild the latestBranch "cache" object by reading the nodes from disk
+     * (When we are opening an existing file on disk and want to append to it,
+     * for example).
+     *
+     * @param rootNodeSeqNb
+     *            The sequence number of the root node, so we know where to
+     *            start
+     * @throws ClosedChannelException
+     */
+    private List<CoreNode> buildLatestBranch(int rootNodeSeqNb) throws ClosedChannelException {
+        HTNode nextChildNode;
+
+        List<CoreNode> list = new ArrayList<>();
+
+        nextChildNode = treeIO.readNode(rootNodeSeqNb);
+        list.add((CoreNode) nextChildNode);
+        while (list.get(list.size() - 1).getNbChildren() > 0) {
+            nextChildNode = treeIO.readNode(list.get(list.size() - 1).getLatestChild());
+            list.add((CoreNode) nextChildNode);
+        }
+        return Collections.synchronizedList(list);
+    }
+
     /**
      * "Save" the tree to disk. This method will cause the treeIO object to
      * commit all nodes to disk and then return the RandomAccessFile descriptor
@@ -211,62 +235,61 @@ public class HistoryTree {
      *            The greatest timestamp present in the history tree
      */
     public void closeTree(long requestedEndTime) {
-        ByteBuffer buffer;
-        int i, res;
-
-        /*
-         * Work-around the "empty branches" that get created when the root node
-         * becomes full. Overwrite the tree's end time with the original wanted
-         * end-time, to ensure no queries are sent into those empty nodes.
-         *
-         * This won't be needed once extended nodes are implemented.
-         */
-        this.treeEnd = requestedEndTime;
+        /* This is an important operation, queries can wait */
+        synchronized (latestBranch) {
+            /*
+             * Work-around the "empty branches" that get created when the root
+             * node becomes full. Overwrite the tree's end time with the
+             * original wanted end-time, to ensure no queries are sent into
+             * those empty nodes.
+             *
+             * This won't be needed once extended nodes are implemented.
+             */
+            this.treeEnd = requestedEndTime;
 
-        /* Close off the latest branch of the tree */
-        for (i = 0; i < latestBranch.size(); i++) {
-            latestBranch.get(i).closeThisNode(treeEnd);
-            treeIO.writeNode(latestBranch.get(i));
-        }
+            /* Close off the latest branch of the tree */
+            for (int i = 0; i < latestBranch.size(); i++) {
+                latestBranch.get(i).closeThisNode(treeEnd);
+                treeIO.writeNode(latestBranch.get(i));
+            }
 
-        try (FileChannel fc = treeIO.getFcOut();) {
-            buffer = ByteBuffer.allocate(TREE_HEADER_SIZE);
-            buffer.order(ByteOrder.LITTLE_ENDIAN);
-            buffer.clear();
+            try (FileChannel fc = treeIO.getFcOut();) {
+                ByteBuffer buffer = ByteBuffer.allocate(TREE_HEADER_SIZE);
+                buffer.order(ByteOrder.LITTLE_ENDIAN);
+                buffer.clear();
 
-            /* Save the config of the tree to the header of the file */
-            fc.position(0);
+                /* Save the config of the tree to the header of the file */
+                fc.position(0);
 
-            buffer.putInt(HISTORY_FILE_MAGIC_NUMBER);
+                buffer.putInt(HISTORY_FILE_MAGIC_NUMBER);
 
-            buffer.putInt(FILE_VERSION);
-            buffer.putInt(config.getProviderVersion());
+                buffer.putInt(FILE_VERSION);
+                buffer.putInt(config.getProviderVersion());
 
-            buffer.putInt(config.getBlockSize());
-            buffer.putInt(config.getMaxChildren());
+                buffer.putInt(config.getBlockSize());
+                buffer.putInt(config.getMaxChildren());
 
-            buffer.putInt(nodeCount);
+                buffer.putInt(nodeCount);
 
-            /* root node seq. nb */
-            buffer.putInt(latestBranch.get(0).getSequenceNumber());
+                /* root node seq. nb */
+                buffer.putInt(latestBranch.get(0).getSequenceNumber());
 
-            /* start time of this history */
-            buffer.putLong(latestBranch.get(0).getNodeStart());
+                /* start time of this history */
+                buffer.putLong(latestBranch.get(0).getNodeStart());
 
-            buffer.flip();
-            res = fc.write(buffer);
-            assert (res <= TREE_HEADER_SIZE);
-            /* done writing the file header */
+                buffer.flip();
+                int res = fc.write(buffer);
+                assert (res <= TREE_HEADER_SIZE);
+                /* done writing the file header */
 
-        } catch (IOException e) {
-            /*
-             * If we were able to write so far, there should not be any problem
-             * at this point...
-             */
-            // FIXME still, the IOException should be propagated upwards
-            throw new RuntimeException();
+            } catch (IOException e) {
+                /*
+                 * If we were able to write so far, there should not be any
+                 * problem at this point...
+                 */
+                throw new RuntimeException("State system write error"); //$NON-NLS-1$
+            }
         }
-        return;
     }
 
     // ------------------------------------------------------------------------
@@ -301,12 +324,12 @@ public class HistoryTree {
     }
 
     /**
-     * Return the latest (right-most) branch of nodes.
+     * Get the current root node of this tree
      *
-     * @return The latest branch
+     * @return The root node
      */
-    public List<CoreNode> getLatestBranch() {
-        return Collections.unmodifiableList(latestBranch);
+    public CoreNode getRootNode() {
+        return latestBranch.get(0);
     }
 
     // ------------------------------------------------------------------------
@@ -355,9 +378,11 @@ public class HistoryTree {
      */
     public HTNode readNode(int seqNumber) throws ClosedChannelException {
         /* Try to read the node from memory */
-        for (HTNode node : getLatestBranch()) {
-            if (node.getSequenceNumber() == seqNumber) {
-                return node;
+        synchronized (latestBranch) {
+            for (HTNode node : latestBranch) {
+                if (node.getSequenceNumber() == seqNumber) {
+                    return node;
+                }
             }
         }
 
@@ -393,29 +418,6 @@ public class HistoryTree {
     // Operations
     // ------------------------------------------------------------------------
 
-    /**
-     * Rebuild the latestBranch "cache" object by reading the nodes from disk
-     * (When we are opening an existing file on disk and want to append to it,
-     * for example).
-     *
-     * @param rootNodeSeqNb
-     *            The sequence number of the root node, so we know where to
-     *            start
-     * @throws ClosedChannelException
-     */
-    private void rebuildLatestBranch(int rootNodeSeqNb) throws ClosedChannelException {
-        HTNode nextChildNode;
-
-        this.latestBranch = new ArrayList<>();
-
-        nextChildNode = treeIO.readNode(rootNodeSeqNb);
-        latestBranch.add((CoreNode) nextChildNode);
-        while (latestBranch.get(latestBranch.size() - 1).getNbChildren() > 0) {
-            nextChildNode = treeIO.readNode(latestBranch.get(latestBranch.size() - 1).getLatestChild());
-            latestBranch.add((CoreNode) nextChildNode);
-        }
-    }
-
     /**
      * Insert an interval in the tree.
      *
@@ -482,36 +484,36 @@ public class HistoryTree {
      *            The index in latestBranch where we start adding
      */
     private void addSiblingNode(int indexOfNode) {
-        int i;
-        CoreNode newNode, prevNode;
-        long splitTime = treeEnd;
+        synchronized (latestBranch) {
+            final long splitTime = treeEnd;
 
-        assert (indexOfNode < latestBranch.size());
+            assert (indexOfNode < latestBranch.size());
 
-        /* Check if we need to add a new root node */
-        if (indexOfNode == 0) {
-            addNewRootNode();
-            return;
-        }
+            /* Check if we need to add a new root node */
+            if (indexOfNode == 0) {
+                addNewRootNode();
+                return;
+            }
 
-        /* Check if we can indeed add a child to the target parent */
-        if (latestBranch.get(indexOfNode - 1).getNbChildren() == config.getMaxChildren()) {
-            /* If not, add a branch starting one level higher instead */
-            addSiblingNode(indexOfNode - 1);
-            return;
-        }
+            /* Check if we can indeed add a child to the target parent */
+            if (latestBranch.get(indexOfNode - 1).getNbChildren() == config.getMaxChildren()) {
+                /* If not, add a branch starting one level higher instead */
+                addSiblingNode(indexOfNode - 1);
+                return;
+            }
 
-        /* Split off the new branch from the old one */
-        for (i = indexOfNode; i < latestBranch.size(); i++) {
-            latestBranch.get(i).closeThisNode(splitTime);
-            treeIO.writeNode(latestBranch.get(i));
+            /* Split off the new branch from the old one */
+            for (int i = indexOfNode; i < latestBranch.size(); i++) {
+                latestBranch.get(i).closeThisNode(splitTime);
+                treeIO.writeNode(latestBranch.get(i));
 
-            prevNode = latestBranch.get(i - 1);
-            newNode = initNewCoreNode(prevNode.getSequenceNumber(),
-                    splitTime + 1);
-            prevNode.linkNewChild(newNode);
+                CoreNode prevNode = latestBranch.get(i - 1);
+                CoreNode newNode = initNewCoreNode(prevNode.getSequenceNumber(),
+                        splitTime + 1);
+                prevNode.linkNewChild(newNode);
 
-            latestBranch.set(i, newNode);
+                latestBranch.set(i, newNode);
+            }
         }
     }
 
@@ -520,18 +522,17 @@ public class HistoryTree {
      * latestBranch
      */
     private void addNewRootNode() {
-        int i, depth;
-        CoreNode oldRootNode, newRootNode, newNode, prevNode;
-        long splitTime = this.treeEnd;
+        final long splitTime = this.treeEnd;
 
-        oldRootNode = latestBranch.get(0);
-        newRootNode = initNewCoreNode(-1, config.getTreeStart());
+        CoreNode oldRootNode = latestBranch.get(0);
+        CoreNode newRootNode = initNewCoreNode(-1, config.getTreeStart());
 
         /* Tell the old root node that it isn't root anymore */
         oldRootNode.setParentSequenceNumber(newRootNode.getSequenceNumber());
 
         /* Close off the whole current latestBranch */
-        for (i = 0; i < latestBranch.size(); i++) {
+
+        for (int i = 0; i < latestBranch.size(); i++) {
             latestBranch.get(i).closeThisNode(splitTime);
             treeIO.writeNode(latestBranch.get(i));
         }
@@ -540,12 +541,12 @@ public class HistoryTree {
         newRootNode.linkNewChild(oldRootNode);
 
         /* Rebuild a new latestBranch */
-        depth = latestBranch.size();
-        latestBranch = new ArrayList<>();
+        int depth = latestBranch.size();
+        latestBranch.clear();
         latestBranch.add(newRootNode);
-        for (i = 1; i < depth + 1; i++) {
-            prevNode = latestBranch.get(i - 1);
-            newNode = initNewCoreNode(prevNode.getParentSequenceNumber(),
+        for (int i = 1; i < depth + 1; i++) {
+            CoreNode prevNode = latestBranch.get(i - 1);
+            CoreNode newNode = initNewCoreNode(prevNode.getParentSequenceNumber(),
                     splitTime + 1);
             prevNode.linkNewChild(newNode);
             latestBranch.add(newNode);
index 0ebd140be099448b016826aa148b19f6b2b3d212..d4d6241b7ac353b8456ab94e418319a444f98185 100644 (file)
@@ -180,7 +180,7 @@ public class HistoryTreeBackend implements IStateHistoryBackend {
         /* We start by reading the information in the root node */
         // FIXME using CoreNode for now, we'll have to redo this part to handle
         // different node types
-        CoreNode currentNode = sht.getLatestBranch().get(0);
+        CoreNode currentNode = sht.getRootNode();
         currentNode.writeInfoFromNode(stateInfo, t);
 
         /* Then we follow the branch down in the relevant children */
@@ -227,7 +227,7 @@ public class HistoryTreeBackend implements IStateHistoryBackend {
 
         // FIXME using CoreNode for now, we'll have to redo this part to handle
         // different node types
-        CoreNode currentNode = sht.getLatestBranch().get(0);
+        CoreNode currentNode = sht.getRootNode();
         HTInterval interval = currentNode.getRelevantInterval(key, t);
 
         try {
@@ -255,15 +255,6 @@ public class HistoryTreeBackend implements IStateHistoryBackend {
         return sht.getFileSize();
     }
 
-    /**
-     * Return the current depth of the tree, ie the number of node levels.
-     *
-     * @return The tree depth
-     */
-    public int getTreeDepth() {
-        return sht.getLatestBranch().size();
-    }
-
     /**
      * Return the average node usage as a percentage (between 0 and 100)
      *
This page took 0.039684 seconds and 5 git commands to generate.