1 /*******************************************************************************
2 * Copyright (c) 2012, 2014 Ericsson
3 * Copyright (c) 2010, 2011 École Polytechnique de Montréal
4 * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
6 * All rights reserved. This program and the accompanying materials are
7 * made available under the terms of the Eclipse Public License v1.0 which
8 * accompanies this distribution, and is available at
9 * http://www.eclipse.org/legal/epl-v10.html
11 *******************************************************************************/
13 package org
.eclipse
.tracecompass
.internal
.statesystem
.core
.backend
.historytree
;
15 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
18 import java
.io
.FileInputStream
;
19 import java
.io
.FileOutputStream
;
20 import java
.io
.IOException
;
21 import java
.nio
.channels
.ClosedChannelException
;
22 import java
.nio
.channels
.FileChannel
;
23 import java
.util
.logging
.Logger
;
25 import org
.eclipse
.jdt
.annotation
.NonNull
;
26 import org
.eclipse
.tracecompass
.common
.core
.log
.TraceCompassLog
;
27 import java
.util
.Objects
;
28 import java
.util
.concurrent
.ExecutionException
;
30 import org
.eclipse
.jdt
.annotation
.Nullable
;
31 import org
.eclipse
.tracecompass
.internal
.statesystem
.core
.Activator
;
33 import com
.google
.common
.cache
.CacheBuilder
;
34 import com
.google
.common
.cache
.CacheLoader
;
35 import com
.google
.common
.cache
.LoadingCache
;
38 * This class abstracts inputs/outputs of the HistoryTree nodes.
40 * It contains all the methods and descriptors to handle reading/writing nodes
41 * to the tree-file on disk and all the caching mechanisms.
43 * This abstraction is mainly for code isolation/clarification purposes. Every
44 * HistoryTree must contain 1 and only 1 HT_IO element.
46 * @author Alexandre Montplaisir
50 private static final Logger LOGGER
= TraceCompassLog
.getLogger(HT_IO
.class);
52 // ------------------------------------------------------------------------
53 // Global cache of nodes
54 // ------------------------------------------------------------------------
56 private static final class CacheKey
{
58 public final HT_IO fStateHistory
;
59 public final int fSeqNumber
;
61 public CacheKey(HT_IO stateHistory
, int seqNumber
) {
62 fStateHistory
= stateHistory
;
63 fSeqNumber
= seqNumber
;
67 public int hashCode() {
68 return Objects
.hash(fStateHistory
, fSeqNumber
);
72 public boolean equals(@Nullable Object obj
) {
79 if (getClass() != obj
.getClass()) {
82 CacheKey other
= (CacheKey
) obj
;
83 return (fStateHistory
.equals(other
.fStateHistory
) &&
84 fSeqNumber
== other
.fSeqNumber
);
88 private static final int CACHE_SIZE
= 256;
90 private static final LoadingCache
<CacheKey
, HTNode
> NODE_CACHE
=
91 checkNotNull(CacheBuilder
.newBuilder()
92 .maximumSize(CACHE_SIZE
)
93 .build(new CacheLoader
<CacheKey
, HTNode
>() {
95 public HTNode
load(CacheKey key
) throws IOException
{
96 HT_IO io
= key
.fStateHistory
;
97 int seqNb
= key
.fSeqNumber
;
99 LOGGER
.finest(() -> "[HtIo:CacheMiss] seqNum=" + seqNb
); //$NON-NLS-1$
101 io
.seekFCToNodePos(io
.fFileChannelIn
, seqNb
);
102 return HTNode
.readNode(io
.fConfig
, io
.fFileChannelIn
);
107 // ------------------------------------------------------------------------
109 // ------------------------------------------------------------------------
111 /* Configuration of the History Tree */
112 private final HTConfig fConfig
;
114 /* Fields related to the file I/O */
115 private final FileInputStream fFileInputStream
;
116 private final FileOutputStream fFileOutputStream
;
117 private final FileChannel fFileChannelIn
;
118 private final FileChannel fFileChannelOut
;
120 // ------------------------------------------------------------------------
122 // ------------------------------------------------------------------------
127 * Standard constructor
130 * The configuration object for the StateHistoryTree
132 * Flag indicating that the file must be created from scratch
134 * @throws IOException
135 * An exception can be thrown when file cannot be accessed
137 public HT_IO(HTConfig config
, boolean newFile
) throws IOException
{
140 File historyTreeFile
= config
.getStateFile();
142 boolean success1
= true;
143 /* Create a new empty History Tree file */
144 if (historyTreeFile
.exists()) {
145 success1
= historyTreeFile
.delete();
147 boolean success2
= historyTreeFile
.createNewFile();
148 if (!(success1
&& success2
)) {
149 /* It seems we do not have permission to create the new file */
150 throw new IOException("Cannot create new file at " + //$NON-NLS-1$
151 historyTreeFile
.getName());
153 fFileInputStream
= new FileInputStream(historyTreeFile
);
154 fFileOutputStream
= new FileOutputStream(historyTreeFile
, false);
157 * We want to open an existing file, make sure we don't squash the
158 * existing content when opening the fos!
160 fFileInputStream
= new FileInputStream(historyTreeFile
);
161 fFileOutputStream
= new FileOutputStream(historyTreeFile
, true);
163 fFileChannelIn
= fFileInputStream
.getChannel();
164 fFileChannelOut
= fFileOutputStream
.getChannel();
168 * Read a node from the file on disk.
171 * The sequence number of the node to read.
172 * @return The object representing the node
173 * @throws ClosedChannelException
174 * Usually happens because the file was closed while we were
175 * reading. Instead of using a big reader-writer lock, we'll
176 * just catch this exception.
178 public synchronized @NonNull HTNode
readNode(int seqNumber
) throws ClosedChannelException
{
179 /* Do a cache lookup. If it's not present it will be loaded from disk */
180 LOGGER
.finest(() -> "[HtIo:CacheLookup] seqNum=" + seqNumber
); //$NON-NLS-1$
181 CacheKey key
= new CacheKey(this, seqNumber
);
183 return checkNotNull(NODE_CACHE
.get(key
));
185 } catch (ExecutionException e
) {
186 /* Get the inner exception that was generated */
187 Throwable cause
= e
.getCause();
188 if (cause
instanceof ClosedChannelException
) {
189 throw (ClosedChannelException
) cause
;
192 * Other types of IOExceptions shouldn't happen at this point though.
194 Activator
.getDefault().logError(e
.getMessage(), e
);
195 throw new IllegalStateException();
199 public synchronized void writeNode(HTNode node
) {
201 int seqNumber
= node
.getSequenceNumber();
203 /* "Write-back" the node into the cache */
204 CacheKey key
= new CacheKey(this, seqNumber
);
205 NODE_CACHE
.put(key
, node
);
207 /* Position ourselves at the start of the node and write it */
208 seekFCToNodePos(fFileChannelOut
, seqNumber
);
209 node
.writeSelf(fFileChannelOut
);
210 } catch (IOException e
) {
211 /* If we were able to open the file, we should be fine now... */
212 Activator
.getDefault().logError(e
.getMessage(), e
);
216 public FileChannel
getFcOut() {
217 return fFileChannelOut
;
220 public FileInputStream
supplyATReader(int nodeOffset
) {
223 * Position ourselves at the start of the Mapping section in the
224 * file (which is right after the Blocks)
226 seekFCToNodePos(fFileChannelIn
, nodeOffset
);
227 } catch (IOException e
) {
228 Activator
.getDefault().logError(e
.getMessage(), e
);
230 return fFileInputStream
;
233 public synchronized void closeFile() {
235 fFileInputStream
.close();
236 fFileOutputStream
.close();
237 } catch (IOException e
) {
238 Activator
.getDefault().logError(e
.getMessage(), e
);
242 public synchronized void deleteFile() {
245 File historyTreeFile
= fConfig
.getStateFile();
246 if (!historyTreeFile
.delete()) {
247 /* We didn't succeed in deleting the file */
248 Activator
.getDefault().logError("Failed to delete" + historyTreeFile
.getName()); //$NON-NLS-1$
253 * Seek the given FileChannel to the position corresponding to the node that
257 * the channel to seek
259 * the node sequence number to seek the channel to
260 * @throws IOException
261 * If some other I/O error occurs
263 private void seekFCToNodePos(FileChannel fc
, int seqNumber
)
266 * Cast to (long) is needed to make sure the result is a long too and
267 * doesn't get truncated
269 fc
.position(HistoryTree
.TREE_HEADER_SIZE
270 + ((long) seqNumber
) * fConfig
.getBlockSize());