2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.util.indexing;
18 import com.intellij.openapi.application.PathManager;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.util.LowMemoryWatcher;
21 import com.intellij.openapi.util.Ref;
22 import com.intellij.openapi.util.ShutDownTracker;
23 import com.intellij.openapi.util.ThrowableComputable;
24 import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
25 import com.intellij.util.SystemProperties;
26 import com.intellij.util.io.*;
27 import com.intellij.util.io.DataOutputStream;
28 import gnu.trove.TIntLongHashMap;
29 import gnu.trove.TIntLongProcedure;
30 import gnu.trove.TIntObjectHashMap;
31 import gnu.trove.TIntObjectProcedure;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
36 import java.util.Arrays;
38 public class SharedIndicesData {
39 private static PersistentHashMap<Integer, byte[]> ourSharedFileInputsIndex;
40 private static PersistentHashMap<Integer, byte[]> ourSharedContentInputsIndex;
41 static final boolean ourFileSharedIndicesEnabled = SystemProperties.getBooleanProperty("idea.shared.input.index.enabled", true);
42 static final boolean DO_CHECKS = ourFileSharedIndicesEnabled && SystemProperties.getBooleanProperty("idea.shared.input.index.checked", true);
44 //private static ScheduledFuture<?> ourFlushingFuture;
45 private static final Logger LOG = Logger.getInstance("#com.intellij.util.indexing.MapReduceIndex");
46 @SuppressWarnings({"FieldCanBeLocal", "unused"}) private static LowMemoryWatcher myLowMemoryCallback;
48 private static final FileAccessorCache<Integer, IndexedState> ourFileIndexedStates = new FileAccessorCache<Integer, IndexedState>(200, 100) {
50 protected IndexedState createAccessor(Integer key) throws IOException {
51 return new IndexedState(key, ourSharedFileInputsIndex);
55 protected void disposeAccessor(IndexedState fileAccessor) throws IOException {
60 private static final FileAccessorCache<Integer, IndexedState> ourContentIndexedStates = new FileAccessorCache<Integer, IndexedState>(200, 100) {
62 protected IndexedState createAccessor(Integer key) throws IOException {
63 return new IndexedState(key, ourSharedContentInputsIndex);
67 protected void disposeAccessor(IndexedState fileAccessor) throws IOException {
73 if (ourFileSharedIndicesEnabled) {
75 myLowMemoryCallback = LowMemoryWatcher.register(new Runnable() {
78 ourFileIndexedStates.clear();
79 ourContentIndexedStates.clear();
82 ourSharedFileInputsIndex = createSharedMap(new File(PathManager.getIndexRoot(), "file_inputs.data"));
83 ourSharedContentInputsIndex = createSharedMap(new File(IndexInfrastructure.getPersistentIndexRoot(), "content_inputs.data"));
85 ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
89 ourSharedFileInputsIndex.close();
90 } catch (IOException ex) {
94 ourSharedContentInputsIndex.close();
95 } catch (IOException ex) {
100 } catch (IOException ex) {
101 throw new RuntimeException(ex);
106 private static PersistentHashMap<Integer, byte[]> createSharedMap(final File indexFile) throws IOException {
107 return IOUtil.openCleanOrResetBroken(
108 new ThrowableComputable<PersistentHashMap<Integer, byte[]>, IOException>() {
110 public PersistentHashMap<Integer, byte[]> compute() throws IOException {
111 return new PersistentHashMap<Integer, byte[]>(indexFile, EnumeratorIntegerDescriptor.INSTANCE,
112 new DataExternalizer<byte[]>() {
114 public void save(@NotNull DataOutput out, byte[] value) throws IOException {
119 public byte[] read(@NotNull DataInput in) throws IOException {
120 int available = ((InputStream)in).available();
121 byte[] result = new byte[available];
122 in.readFully(result);
130 public static void init() {
134 public static void flushData() {
135 if (!ourFileSharedIndicesEnabled) return;
136 ourFileIndexedStates.clear();
137 if (ourSharedFileInputsIndex != null && ourSharedFileInputsIndex.isDirty()) ourSharedFileInputsIndex.force();
138 ourContentIndexedStates.clear();
139 if (ourSharedContentInputsIndex != null && ourSharedContentInputsIndex.isDirty()) ourSharedContentInputsIndex.force();
142 public static void beforeSomeIndexVersionInvalidation() {
146 static class IndexedState {
147 private final int fileOrContentId;
148 private final PersistentHashMap<Integer, byte[]> storage;
150 private byte[] values;
151 private TIntLongHashMap indexId2Offset;
152 private TIntObjectHashMap<byte[]> indexId2NewState;
153 private boolean compactNecessary;
155 IndexedState(int fileOrContentId, PersistentHashMap<Integer, byte[]> storage) throws IOException {
156 this.fileOrContentId = fileOrContentId;
157 this.storage = storage;
158 byte[] bytes = storage.get(fileOrContentId);
163 DataInputStream stream = new DataInputStream(new UnsyncByteArrayInputStream(bytes));
164 boolean compactNecessary = false;
165 TIntLongHashMap stateMap = null;
167 while(stream.available() > 0) {
168 int chunkSize = DataInputOutputUtil.readINT(stream);
169 int chunkIndexId = DataInputOutputUtil.readINT(stream);
170 long chunkIndexTimeStamp = DataInputOutputUtil.readTIME(stream);
171 int currentOffset = bytes.length - stream.available();
173 ID<?, ?> chunkIndexID;
174 if (((chunkIndexID = ID.findById(chunkIndexId)) != null &&
175 chunkIndexTimeStamp == IndexingStamp.getIndexCreationStamp(chunkIndexID))
177 if (chunkSize != 0) {
178 if (stateMap == null) stateMap = new TIntLongHashMap();
179 stateMap.put(chunkIndexId, (((long)currentOffset) << 32) | chunkSize);
180 } else if (stateMap != null) {
181 stateMap.remove(chunkIndexId);
182 compactNecessary = true;
185 compactNecessary = true;
188 stream.skipBytes(chunkSize);
191 this.compactNecessary = compactNecessary;
192 indexId2Offset = stateMap;
195 synchronized void flush() throws IOException {
196 if (compactNecessary) {
197 //noinspection IOResourceOpenedButNotSafelyClosed
198 UnsyncByteArrayOutputStream compactedOutputStream = new UnsyncByteArrayOutputStream(values.length);
199 //noinspection IOResourceOpenedButNotSafelyClosed
200 DataOutput compactedOutput = new DataOutputStream(compactedOutputStream);
202 Ref<IOException> ioExceptionRef = new Ref<>();
204 boolean result = indexId2NewState == null || indexId2NewState.forEachEntry(new TIntObjectProcedure<byte[]>() {
206 public boolean execute(int indexUniqueId, byte[] indexValue) {
208 long indexCreationStamp = IndexingStamp.getIndexCreationStamp(ID.findById(indexUniqueId));
210 writeIndexValue(indexUniqueId, indexCreationStamp, indexValue, 0, indexValue.length, compactedOutput);
214 catch (IOException ex) {
215 ioExceptionRef.set(ex);
220 if (!result) throw ioExceptionRef.get();
222 result = indexId2Offset == null || indexId2Offset.forEachEntry(new TIntLongProcedure() {
224 public boolean execute(int chunkIndexId, long chunkOffsetAndSize) {
226 int chunkOffset = (int)(chunkOffsetAndSize >> 32);
227 int chunkSize = (int)chunkOffsetAndSize;
231 IndexingStamp.getIndexCreationStamp(ID.findById(chunkIndexId)),
240 catch (IOException e) {
241 ioExceptionRef.set(e);
246 if (!result) throw ioExceptionRef.get();
247 if (compactedOutputStream.size() > 0) storage.put(fileOrContentId, compactedOutputStream.toByteArray());
248 else storage.remove(fileOrContentId);
252 // todo: what about handling changed indices' versions
253 synchronized void appendIndexedState(ID<?, ?> indexId, long timestamp, byte[] buffer, int size) {
254 int indexUniqueId = indexId.getUniqueId();
256 if (indexId2Offset != null) indexId2Offset.remove(indexUniqueId);
257 if (buffer == null) {
258 if (indexId2NewState != null) indexId2NewState.remove(indexUniqueId);
260 if (indexId2NewState == null) indexId2NewState = new TIntObjectHashMap<>();
261 indexId2NewState.put(indexUniqueId, Arrays.copyOf(buffer, size));
265 synchronized @Nullable DataInputStream readIndexedState(ID<?, ?> indexId) {
266 int indexUniqueId = indexId.getUniqueId();
271 if (indexId2NewState != null) { // newdata
272 bytes = indexId2NewState.get(indexUniqueId);
274 length = bytes != null ? bytes.length : 0;
278 if (values == null || // empty
279 indexId2Offset == null ||
280 !indexId2Offset.contains(indexUniqueId) // no previous data
285 long offsetAndSize = indexId2Offset.get(indexUniqueId);
286 offset = (int)(offsetAndSize >> 32);
287 length = (int)offsetAndSize;
290 return new DataInputStream(new UnsyncByteArrayInputStream(bytes, offset, offset + length));
294 private static void writeIndexValue(int indexUniqueId,
295 long indexCreationStamp,
297 int indexValueOffset, int indexValueLength,
298 DataOutput compactedOutput) throws IOException {
299 DataInputOutputUtil.writeINT(compactedOutput, indexValueLength);
300 DataInputOutputUtil.writeINT(compactedOutput, indexUniqueId);
302 DataInputOutputUtil.writeTIME(compactedOutput, indexCreationStamp);
303 if (indexValue != null) {
304 assert indexValueLength > 0;
305 compactedOutput.write(indexValue, indexValueOffset, indexValueLength);
308 assert indexValueLength == 0;
312 // Record: (<chunkSize> <indexId> <indexStamp> <SavedData>)*
314 public static @Nullable <Key, Value> Value recallFileData(int id, ID<Key, ?> indexId, DataExternalizer<Value> externalizer)
316 return doRecallData(id, indexId, externalizer, ourFileIndexedStates);
319 public static @Nullable <Key, Value> Value recallContentData(int id, ID<Key, ?> indexId, DataExternalizer<Value> externalizer)
321 return doRecallData(id, indexId, externalizer, ourContentIndexedStates);
325 private static <Key, Value> Value doRecallData(int id,
327 DataExternalizer<Value> externalizer,
328 FileAccessorCache<Integer, IndexedState> states)
330 FileAccessorCache.Handle<IndexedState> stateHandle = states.get(id);
331 IndexedState indexedState = stateHandle.get();
334 DataInputStream in = indexedState.readIndexedState(indexId);
335 if (in == null) return null;
336 return externalizer.read(in);
338 stateHandle.release();
342 public static <Key, Value> void associateFileData(int id, ID<Key, ?> indexId, Value keys, DataExternalizer<Value> externalizer)
344 doAssociateData(id, indexId, keys, externalizer, ourFileIndexedStates, ourSharedFileInputsIndex);
347 public static <Key, Value> void associateContentData(int id, ID<Key, ?> indexId, Value keys, DataExternalizer<Value> externalizer)
349 doAssociateData(id, indexId, keys, externalizer, ourContentIndexedStates, ourSharedContentInputsIndex);
352 private static <Key, Value> void doAssociateData(int id,
353 final ID<Key, ?> indexId,
355 DataExternalizer<Value> externalizer,
356 FileAccessorCache<Integer, IndexedState> states,
357 PersistentHashMap<Integer, byte[]> index)
359 final BufferExposingByteArrayOutputStream savedKeysData;
361 //noinspection IOResourceOpenedButNotSafelyClosed
362 externalizer.save(new DataOutputStream(savedKeysData = new BufferExposingByteArrayOutputStream()), keys);
364 savedKeysData = null;
367 FileAccessorCache.Handle<IndexedState> stateHandle = states.getIfCached(id);
370 index.appendData(id, new PersistentHashMap.ValueDataAppender() {
372 public void append(DataOutput out) throws IOException {
373 byte[] internalBuffer = null;
375 if (savedKeysData != null) {
376 internalBuffer = savedKeysData.getInternalBuffer();
377 size = savedKeysData.size();
380 long indexCreationStamp = IndexingStamp.getIndexCreationStamp(indexId);
382 indexId.getUniqueId(),
390 final IndexedState indexedState = stateHandle != null ? stateHandle.get() : null;
391 if (indexedState != null) {
392 indexedState.appendIndexedState(indexId, indexCreationStamp, internalBuffer, size);
397 if (stateHandle != null) stateHandle.release();