Commit | Line | Data |
---|---|---|
a52fde77 | 1 | /******************************************************************************* |
60ae41e1 | 2 | * Copyright (c) 2012, 2014 Ericsson |
a52fde77 AM |
3 | * Copyright (c) 2010, 2011 École Polytechnique de Montréal |
4 | * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com> | |
3de60236 | 5 | * |
a52fde77 AM |
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 | |
3de60236 | 10 | * |
a52fde77 AM |
11 | *******************************************************************************/ |
12 | ||
e894a508 | 13 | package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; |
a52fde77 AM |
14 | |
15 | import java.io.File; | |
16 | import java.io.FileInputStream; | |
17 | import java.io.FileOutputStream; | |
18 | import java.io.IOException; | |
3b7f5abe | 19 | import java.nio.channels.ClosedChannelException; |
a52fde77 | 20 | import java.nio.channels.FileChannel; |
b2648641 | 21 | import java.util.logging.Logger; |
a52fde77 | 22 | |
aa353506 | 23 | import org.eclipse.jdt.annotation.NonNull; |
4018d70a | 24 | import org.eclipse.jdt.annotation.NonNullByDefault; |
b2648641 | 25 | import org.eclipse.tracecompass.common.core.log.TraceCompassLog; |
49a817d5 MK |
26 | import org.eclipse.tracecompass.internal.statesystem.core.Activator; |
27 | ||
a52fde77 | 28 | /** |
360f899e EB |
29 | * This class abstracts inputs/outputs of the HistoryTree nodes. |
30 | * | |
31 | * It contains all the methods and descriptors to handle reading/writing nodes | |
32 | * to the tree-file on disk and all the caching mechanisms. | |
33 | * | |
49a817d5 MK |
34 | * This abstraction is mainly for code isolation/clarification purposes. Every |
35 | * HistoryTree must contain 1 and only 1 HT_IO element. | |
3de60236 | 36 | * |
ffd0aa67 | 37 | * @author Alexandre Montplaisir |
a52fde77 AM |
38 | */ |
39 | class HT_IO { | |
4018d70a MK |
40 | |
41 | @NonNullByDefault | |
42 | private static final class CacheElement { | |
43 | private final HTNode value; | |
44 | private final HT_IO key; | |
45 | ||
46 | public CacheElement(HT_IO ss, HTNode node) { | |
47 | key = ss; | |
48 | value = node; | |
49 | } | |
50 | ||
51 | public HT_IO getKey() { | |
52 | return key; | |
53 | } | |
54 | ||
55 | public HTNode getValue() { | |
56 | return value; | |
57 | } | |
58 | } | |
59 | ||
360f899e EB |
60 | /* Configuration of the History Tree */ |
61 | private final HTConfig fConfig; | |
a52fde77 AM |
62 | |
63 | /* Fields related to the file I/O */ | |
c772ff74 MK |
64 | private final FileInputStream fFileInputStream; |
65 | private final FileOutputStream fFileOutputStream; | |
66 | private final FileChannel fFileChannelIn; | |
67 | private final FileChannel fFileChannelOut; | |
a52fde77 | 68 | |
0c1d4577 | 69 | // TODO test/benchmark optimal cache size |
c772ff74 MK |
70 | /** |
71 | * Cache size, must be a power of 2 | |
72 | */ | |
49a817d5 | 73 | private static final int CACHE_SIZE = 256; |
c772ff74 | 74 | private static final int CACHE_MASK = CACHE_SIZE - 1; |
4018d70a | 75 | private static final CacheElement NODE_CACHE[] = new CacheElement[CACHE_SIZE]; |
6993c1ca | 76 | |
b2648641 GB |
77 | private static final Logger LOGGER = TraceCompassLog.getLogger(HT_IO.class); |
78 | ||
a52fde77 AM |
79 | /** |
80 | * Standard constructor | |
3de60236 | 81 | * |
360f899e | 82 | * @param config |
49a817d5 | 83 | * The configuration object for the StateHistoryTree |
ab604305 | 84 | * @param newFile |
360f899e | 85 | * Flag indicating that the file must be created from scratch |
49a817d5 | 86 | * |
a52fde77 | 87 | * @throws IOException |
360f899e | 88 | * An exception can be thrown when file cannot be accessed |
a52fde77 | 89 | */ |
8d47cc34 | 90 | public HT_IO(HTConfig config, boolean newFile) throws IOException { |
360f899e | 91 | fConfig = config; |
a52fde77 | 92 | |
360f899e | 93 | File historyTreeFile = config.getStateFile(); |
a52fde77 | 94 | if (newFile) { |
360f899e | 95 | boolean success1 = true; |
a52fde77 AM |
96 | /* Create a new empty History Tree file */ |
97 | if (historyTreeFile.exists()) { | |
ab604305 AM |
98 | success1 = historyTreeFile.delete(); |
99 | } | |
cb42195c | 100 | boolean success2 = historyTreeFile.createNewFile(); |
ab604305 AM |
101 | if (!(success1 && success2)) { |
102 | /* It seems we do not have permission to create the new file */ | |
103 | throw new IOException("Cannot create new file at " + //$NON-NLS-1$ | |
104 | historyTreeFile.getName()); | |
a52fde77 | 105 | } |
c772ff74 MK |
106 | fFileInputStream = new FileInputStream(historyTreeFile); |
107 | fFileOutputStream = new FileOutputStream(historyTreeFile, false); | |
a52fde77 AM |
108 | } else { |
109 | /* | |
110 | * We want to open an existing file, make sure we don't squash the | |
111 | * existing content when opening the fos! | |
112 | */ | |
c772ff74 MK |
113 | fFileInputStream = new FileInputStream(historyTreeFile); |
114 | fFileOutputStream = new FileOutputStream(historyTreeFile, true); | |
a52fde77 | 115 | } |
c772ff74 MK |
116 | fFileChannelIn = fFileInputStream.getChannel(); |
117 | fFileChannelOut = fFileOutputStream.getChannel(); | |
a52fde77 AM |
118 | } |
119 | ||
a52fde77 | 120 | /** |
8d47cc34 | 121 | * Read a node from the file on disk. |
3b7f5abe | 122 | * |
8d47cc34 AM |
123 | * @param seqNumber |
124 | * The sequence number of the node to read. | |
125 | * @return The object representing the node | |
3b7f5abe AM |
126 | * @throws ClosedChannelException |
127 | * Usually happens because the file was closed while we were | |
128 | * reading. Instead of using a big reader-writer lock, we'll | |
129 | * just catch this exception. | |
a52fde77 | 130 | */ |
aa353506 | 131 | public synchronized @NonNull HTNode readNode(int seqNumber) throws ClosedChannelException { |
6993c1ca | 132 | /* Do a cache lookup */ |
4018d70a MK |
133 | int offset = (seqNumber + hashCode()) & CACHE_MASK; |
134 | CacheElement cachedNode = NODE_CACHE[offset]; | |
135 | ||
136 | if (cachedNode != null && cachedNode.getKey() == this && cachedNode.getValue().getSequenceNumber() == seqNumber) { | |
b2648641 | 137 | LOGGER.finest(() -> "[HtIo:CacheHit] seqNum=" + seqNumber); //$NON-NLS-1$ |
4018d70a | 138 | return cachedNode.getValue(); |
6993c1ca EB |
139 | } |
140 | ||
141 | /* Lookup on disk */ | |
a52fde77 | 142 | try { |
c772ff74 | 143 | seekFCToNodePos(fFileChannelIn, seqNumber); |
4018d70a | 144 | HTNode readNode = HTNode.readNode(fConfig, fFileChannelIn); |
b2648641 | 145 | LOGGER.finest(() -> "[HtIo:CacheMiss] seqNum=" + seqNumber); //$NON-NLS-1$ |
6993c1ca EB |
146 | |
147 | /* Put the node in the cache. */ | |
4018d70a | 148 | NODE_CACHE[offset] = new CacheElement(this, readNode); |
a52fde77 | 149 | return readNode; |
aa353506 | 150 | |
3b7f5abe AM |
151 | } catch (ClosedChannelException e) { |
152 | throw e; | |
a52fde77 | 153 | } catch (IOException e) { |
4018d70a MK |
154 | /* |
155 | * Other types of IOExceptions shouldn't happen at this point though | |
156 | */ | |
49a817d5 | 157 | Activator.getDefault().logError(e.getMessage(), e); |
aa353506 | 158 | throw new IllegalStateException(); |
a52fde77 AM |
159 | } |
160 | } | |
161 | ||
e8b7cc14 | 162 | public synchronized void writeNode(HTNode node) { |
a52fde77 | 163 | try { |
6993c1ca EB |
164 | /* Insert the node into the cache. */ |
165 | int seqNumber = node.getSequenceNumber(); | |
4018d70a MK |
166 | int offset = (seqNumber + hashCode()) & CACHE_MASK; |
167 | NODE_CACHE[offset] = new CacheElement(this, node); | |
6993c1ca | 168 | |
a52fde77 | 169 | /* Position ourselves at the start of the node and write it */ |
c772ff74 MK |
170 | seekFCToNodePos(fFileChannelOut, seqNumber); |
171 | node.writeSelf(fFileChannelOut); | |
a52fde77 AM |
172 | } catch (IOException e) { |
173 | /* If we were able to open the file, we should be fine now... */ | |
49a817d5 | 174 | Activator.getDefault().logError(e.getMessage(), e); |
a52fde77 AM |
175 | } |
176 | } | |
177 | ||
e8b7cc14 | 178 | public FileChannel getFcOut() { |
c772ff74 | 179 | return fFileChannelOut; |
a52fde77 AM |
180 | } |
181 | ||
8d47cc34 | 182 | public FileInputStream supplyATReader(int nodeOffset) { |
a52fde77 AM |
183 | try { |
184 | /* | |
185 | * Position ourselves at the start of the Mapping section in the | |
186 | * file (which is right after the Blocks) | |
187 | */ | |
c772ff74 | 188 | seekFCToNodePos(fFileChannelIn, nodeOffset); |
a52fde77 | 189 | } catch (IOException e) { |
49a817d5 | 190 | Activator.getDefault().logError(e.getMessage(), e); |
a52fde77 | 191 | } |
c772ff74 | 192 | return fFileInputStream; |
a52fde77 AM |
193 | } |
194 | ||
8d47cc34 | 195 | public synchronized void closeFile() { |
3de60236 | 196 | try { |
c772ff74 MK |
197 | fFileInputStream.close(); |
198 | fFileOutputStream.close(); | |
3de60236 | 199 | } catch (IOException e) { |
49a817d5 | 200 | Activator.getDefault().logError(e.getMessage(), e); |
3de60236 | 201 | } |
1a4205d9 AM |
202 | } |
203 | ||
8d47cc34 | 204 | public synchronized void deleteFile() { |
1a4205d9 | 205 | closeFile(); |
3de60236 | 206 | |
360f899e | 207 | File historyTreeFile = fConfig.getStateFile(); |
6993c1ca | 208 | if (!historyTreeFile.delete()) { |
36bf82a2 | 209 | /* We didn't succeed in deleting the file */ |
49a817d5 | 210 | Activator.getDefault().logError("Failed to delete" + historyTreeFile.getName()); //$NON-NLS-1$ |
36bf82a2 AM |
211 | } |
212 | } | |
213 | ||
a52fde77 AM |
214 | /** |
215 | * Seek the given FileChannel to the position corresponding to the node that | |
216 | * has seqNumber | |
3de60236 | 217 | * |
49a817d5 MK |
218 | * @param fc |
219 | * the channel to seek | |
220 | * @param seqNumber | |
221 | * the node sequence number to seek the channel to | |
a52fde77 | 222 | * @throws IOException |
49a817d5 | 223 | * If some other I/O error occurs |
a52fde77 AM |
224 | */ |
225 | private void seekFCToNodePos(FileChannel fc, int seqNumber) | |
226 | throws IOException { | |
a52fde77 | 227 | /* |
360f899e | 228 | * Cast to (long) is needed to make sure the result is a long too and |
a52fde77 AM |
229 | * doesn't get truncated |
230 | */ | |
360f899e | 231 | fc.position(HistoryTree.TREE_HEADER_SIZE |
49a817d5 | 232 | + ((long) seqNumber) * fConfig.getBlockSize()); |
a52fde77 AM |
233 | } |
234 | ||
235 | } |