1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.util.indexing;
4 import com.google.common.annotations.VisibleForTesting;
5 import com.intellij.AppTopics;
6 import com.intellij.ide.AppLifecycleListener;
7 import com.intellij.ide.plugins.DynamicPluginListener;
8 import com.intellij.ide.startup.ServiceNotReadyException;
9 import com.intellij.model.ModelBranch;
10 import com.intellij.openapi.actionSystem.ex.ActionUtil;
11 import com.intellij.openapi.application.AppUIExecutor;
12 import com.intellij.openapi.application.Application;
13 import com.intellij.openapi.application.ApplicationManager;
14 import com.intellij.openapi.application.ReadAction;
15 import com.intellij.openapi.diagnostic.Logger;
16 import com.intellij.openapi.editor.Document;
17 import com.intellij.openapi.editor.impl.EditorHighlighterCache;
18 import com.intellij.openapi.extensions.ExtensionPointListener;
19 import com.intellij.openapi.extensions.PluginDescriptor;
20 import com.intellij.openapi.fileEditor.FileDocumentManager;
21 import com.intellij.openapi.fileEditor.FileDocumentManagerListener;
22 import com.intellij.openapi.fileTypes.FileType;
23 import com.intellij.openapi.fileTypes.FileTypeManager;
24 import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl;
25 import com.intellij.openapi.progress.ProcessCanceledException;
26 import com.intellij.openapi.progress.ProgressIndicator;
27 import com.intellij.openapi.progress.ProgressManager;
28 import com.intellij.openapi.progress.Task;
29 import com.intellij.openapi.project.*;
30 import com.intellij.openapi.util.*;
31 import com.intellij.openapi.util.io.FileUtil;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.openapi.vfs.VirtualFileWithId;
34 import com.intellij.openapi.vfs.newvfs.AsyncEventSupport;
35 import com.intellij.openapi.vfs.newvfs.ManagingFS;
36 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
37 import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry;
38 import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon;
39 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
40 import com.intellij.psi.*;
41 import com.intellij.psi.impl.PsiDocumentTransactionListener;
42 import com.intellij.psi.impl.PsiManagerImpl;
43 import com.intellij.psi.impl.PsiTreeChangeEventImpl;
44 import com.intellij.psi.impl.cache.impl.id.PlatformIdTableBuilding;
45 import com.intellij.psi.impl.source.PsiFileImpl;
46 import com.intellij.psi.search.GlobalSearchScope;
47 import com.intellij.psi.stubs.SerializationManagerEx;
48 import com.intellij.psi.util.CachedValueProvider;
49 import com.intellij.psi.util.CachedValuesManager;
50 import com.intellij.util.*;
51 import com.intellij.util.containers.ContainerUtil;
52 import com.intellij.util.containers.FactoryMap;
53 import com.intellij.util.gist.GistManager;
54 import com.intellij.util.indexing.contentQueue.CachedFileContent;
55 import com.intellij.util.indexing.diagnostic.FileIndexingStatistics;
56 import com.intellij.util.indexing.impl.MapReduceIndex;
57 import com.intellij.util.indexing.impl.storage.TransientChangesIndexStorage;
58 import com.intellij.util.indexing.impl.storage.VfsAwareMapIndexStorage;
59 import com.intellij.util.indexing.impl.storage.VfsAwareMapReduceIndex;
60 import com.intellij.util.indexing.memory.InMemoryIndexStorage;
61 import com.intellij.util.indexing.snapshot.SnapshotHashEnumeratorService;
62 import com.intellij.util.indexing.snapshot.SnapshotInputMappings;
63 import com.intellij.util.indexing.snapshot.SnapshotSingleValueIndexStorage;
64 import com.intellij.util.io.storage.HeavyProcessLatch;
65 import com.intellij.util.messages.SimpleMessageBusConnection;
66 import it.unimi.dsi.fastutil.ints.IntArrayList;
67 import org.jetbrains.annotations.ApiStatus;
68 import org.jetbrains.annotations.NotNull;
69 import org.jetbrains.annotations.Nullable;
70 import org.jetbrains.annotations.TestOnly;
73 import java.io.IOException;
74 import java.lang.ref.SoftReference;
75 import java.lang.ref.WeakReference;
77 import java.util.concurrent.ScheduledFuture;
78 import java.util.concurrent.atomic.AtomicInteger;
79 import java.util.concurrent.locks.Lock;
80 import java.util.concurrent.locks.ReadWriteLock;
81 import java.util.concurrent.locks.ReentrantLock;
82 import java.util.concurrent.locks.ReentrantReadWriteLock;
83 import java.util.function.IntPredicate;
84 import java.util.function.Predicate;
85 import java.util.stream.Collectors;
86 import java.util.stream.Stream;
88 public final class FileBasedIndexImpl extends FileBasedIndexEx {
89 private static final ThreadLocal<VirtualFile> ourIndexedFile = new ThreadLocal<>();
90 private static final ThreadLocal<VirtualFile> ourFileToBeIndexed = new ThreadLocal<>();
91 public static final Logger LOG = Logger.getInstance(FileBasedIndexImpl.class);
93 private volatile RegisteredIndexes myRegisteredIndexes;
95 private final PerIndexDocumentVersionMap myLastIndexedDocStamps = new PerIndexDocumentVersionMap();
97 // findExtensionOrFail is thread safe
98 private final NotNullLazyValue<ChangedFilesCollector> myChangedFilesCollector = NotNullLazyValue.createValue(()
99 -> AsyncEventSupport.EP_NAME.findExtensionOrFail(ChangedFilesCollector.class));
101 private final List<IndexableFileSet> myIndexableSets = ContainerUtil.createLockFreeCopyOnWriteList();
102 private final Map<IndexableFileSet, Project> myIndexableSetToProjectMap = new HashMap<>();
104 private final SimpleMessageBusConnection myConnection;
105 private final FileDocumentManager myFileDocumentManager;
107 private final Set<ID<?, ?>> myUpToDateIndicesForUnsavedOrTransactedDocuments = ContainerUtil.newConcurrentSet();
108 private volatile SmartFMap<Document, PsiFile> myTransactionMap = SmartFMap.emptyMap();
110 private final boolean myIsUnitTestMode;
113 private Runnable myShutDownTask;
115 private ScheduledFuture<?> myFlushingFuture;
117 private final AtomicInteger myLocalModCount = new AtomicInteger();
118 private final AtomicInteger myFilesModCount = new AtomicInteger();
119 private final Set<Project> myProjectsBeingUpdated = ContainerUtil.newConcurrentSet();
121 private final Lock myReadLock;
122 final Lock myWriteLock;
124 private IndexConfiguration getState() {
125 return myRegisteredIndexes.getConfigurationState();
128 void dropRegisteredIndexes() {
129 ScheduledFuture<?> flushingFuture = myFlushingFuture;
130 LOG.assertTrue(flushingFuture == null || flushingFuture.isCancelled() || flushingFuture.isDone());
131 LOG.assertTrue(myUpToDateIndicesForUnsavedOrTransactedDocuments.isEmpty());
132 LOG.assertTrue(myProjectsBeingUpdated.isEmpty());
133 LOG.assertTrue(!getChangedFilesCollector().isUpdateInProgress());
134 LOG.assertTrue(myTransactionMap.isEmpty());
136 myRegisteredIndexes = null;
139 public FileBasedIndexImpl() {
140 ReadWriteLock lock = new ReentrantReadWriteLock();
141 myReadLock = lock.readLock();
142 myWriteLock = lock.writeLock();
144 myFileDocumentManager = FileDocumentManager.getInstance();
145 myIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
147 SimpleMessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().simpleConnect();
148 connection.subscribe(DynamicPluginListener.TOPIC, new FileBasedIndexPluginListener(this));
150 connection.subscribe(PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() {
152 public void transactionStarted(@NotNull final Document doc, @NotNull final PsiFile file) {
153 myTransactionMap = myTransactionMap.plus(doc, file);
154 clearUpToDateIndexesForUnsavedOrTransactedDocs();
158 public void transactionCompleted(@NotNull final Document doc, @NotNull final PsiFile file) {
159 myTransactionMap = myTransactionMap.minus(doc);
163 connection.subscribe(FileTypeManager.TOPIC, new FileBasedIndexFileTypeListener());
165 connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerListener() {
167 public void fileContentReloaded(@NotNull VirtualFile file, @NotNull Document document) {
168 cleanupMemoryStorage(true);
172 public void unsavedDocumentsDropped() {
173 cleanupMemoryStorage(false);
177 connection.subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener() {
179 public void appWillBeClosed(boolean isRestart) {
180 if (!myRegisteredIndexes.areIndexesReady()) {
181 new Task.Modal(null, IndexingBundle.message("indexes.preparing.to.shutdown.message"), false) {
183 public void run(@NotNull ProgressIndicator indicator) {
184 myRegisteredIndexes.waitUntilAllIndicesAreInitialized();
191 myConnection = connection;
193 FileBasedIndexExtension.EXTENSION_POINT_NAME.addExtensionPointListener(new ExtensionPointListener<FileBasedIndexExtension<?, ?>>() {
195 public void extensionRemoved(@NotNull FileBasedIndexExtension<?, ?> extension, @NotNull PluginDescriptor pluginDescriptor) {
196 ID.unloadId(extension.getName());
203 void scheduleFullIndexesRescan(@NotNull Collection<ID<?, ?>> indexesToRebuild, @NotNull String reason) {
204 cleanupProcessedFlag();
205 doClearIndices(id -> indexesToRebuild.contains(id));
206 scheduleIndexRebuild(reason);
210 void doClearIndices(@NotNull Predicate<? super ID<?, ?>> filter) {
212 waitUntilIndicesAreInitialized();
214 catch (ProcessCanceledException e) {
215 // will be rebuilt on re-scan
218 IndexingStamp.flushCaches();
220 List<ID<?, ?>> clearedIndexes = new ArrayList<>();
221 List<ID<?, ?>> survivedIndexes = new ArrayList<>();
222 for (ID<?, ?> indexId : getState().getIndexIDs()) {
223 if (filter.test(indexId)) {
226 } catch (StorageException e) {
228 } catch (Exception e) {
231 clearedIndexes.add(indexId);
233 survivedIndexes.add(indexId);
237 LOG.info("indexes cleared: " + clearedIndexes.stream().map(id -> id.getName()).collect(Collectors.joining(", ")) + "\n" +
238 "survived indexes: " + survivedIndexes.stream().map(id -> id.getName()).collect(Collectors.joining(", ")));
241 boolean processChangedFiles(@NotNull Project project, @NotNull Processor<? super VirtualFile> processor) {
242 // avoid missing files when events are processed concurrently
243 return Stream.concat(getChangedFilesCollector().getEventMerger().getChangedFiles(),
244 getChangedFilesCollector().getFilesToUpdate())
245 .filter(filesToBeIndexedForProjectCondition(project))
247 .mapToInt(f -> processor.process(f) ? 1 : 0)
248 .allMatch(success -> success == 1);
251 RegisteredIndexes getRegisteredIndexes() {
252 return myRegisteredIndexes;
255 void setUpShutDownTask() {
256 myShutDownTask = new MyShutDownTask();
257 ShutDownTracker.getInstance().registerShutdownTask(myShutDownTask);
261 public void dumpIndexStatistics() {
262 IndexConfiguration state = getRegisteredIndexes().getState();
263 for (ID<?, ?> id : state.getIndexIDs()) {
264 state.getIndex(id).dumpStatistics();
268 static class MyShutDownTask implements Runnable {
271 FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance();
272 if (fileBasedIndex instanceof FileBasedIndexImpl) {
273 ((FileBasedIndexImpl)fileBasedIndex).performShutdown(false);
278 public static boolean isProjectOrWorkspaceFile(@NotNull VirtualFile file, @Nullable FileType fileType) {
279 return ProjectCoreUtil.isProjectOrWorkspaceFile(file, fileType);
282 static boolean belongsToScope(VirtualFile file, VirtualFile restrictedTo, GlobalSearchScope filter) {
283 if (!(file instanceof VirtualFileWithId) || !file.isValid()) {
287 return (restrictedTo == null || Comparing.equal(file, restrictedTo)) &&
288 (filter == null || restrictedTo != null || filter.accept(file));
292 public void requestReindex(@NotNull VirtualFile file) {
293 requestReindex(file, true);
297 public void requestReindex(@NotNull VirtualFile file, boolean forceRebuildRequest) {
298 GistManager.getInstance().invalidateData(file);
299 // todo: this is the same vfs event handling sequence that is produces after events of FileContentUtilCore.reparseFiles
300 // but it is more costly than current code, see IDEA-192192
301 //myChangedFilesCollector.invalidateIndicesRecursively(file, false);
302 //myChangedFilesCollector.buildIndicesForFileRecursively(file, false);
303 ChangedFilesCollector changedFilesCollector = getChangedFilesCollector();
304 changedFilesCollector.invalidateIndicesRecursively(file, true, forceRebuildRequest, changedFilesCollector.getEventMerger());
305 if (myRegisteredIndexes.isInitialized()) {
306 changedFilesCollector.ensureUpToDateAsync();
310 void initComponent() {
311 LOG.assertTrue(myRegisteredIndexes == null);
312 myStorageBufferingHandler.resetState();
313 myRegisteredIndexes = new RegisteredIndexes(myFileDocumentManager, this);
317 public void waitUntilIndicesAreInitialized() {
318 if (myRegisteredIndexes == null) {
319 // interrupt all calculation while plugin reload
320 throw new ProcessCanceledException();
322 myRegisteredIndexes.waitUntilIndicesAreInitialized();
325 static <K, V> void registerIndexer(@NotNull final FileBasedIndexExtension<K, V> extension, @NotNull IndexConfiguration state,
326 @NotNull IndexVersionRegistrationSink registrationStatusSink) throws IOException {
327 ID<K, V> name = extension.getName();
328 int version = getIndexExtensionVersion(extension);
330 final File versionFile = IndexInfrastructure.getVersionFile(name);
332 IndexingStamp.IndexVersionDiff diff = IndexingStamp.versionDiffers(name, version);
333 registrationStatusSink.setIndexVersionDiff(name, diff);
334 if (diff != IndexingStamp.IndexVersionDiff.UP_TO_DATE) {
335 final boolean versionFileExisted = versionFile.exists();
337 if (extension.hasSnapshotMapping() && versionFileExisted) {
338 FileUtil.deleteWithRenaming(IndexInfrastructure.getPersistentIndexRootDir(name));
340 File rootDir = IndexInfrastructure.getIndexRootDir(name);
341 if (versionFileExisted) FileUtil.deleteWithRenaming(rootDir);
342 IndexingStamp.rewriteVersion(name, version);
345 if (versionFileExisted) {
346 for (FileBasedIndexInfrastructureExtension ex : FileBasedIndexInfrastructureExtension.EP_NAME.getExtensionList()) {
347 ex.onFileBasedIndexVersionChanged(name);
350 } catch (Exception e) {
355 initIndexStorage(extension, version, state, registrationStatusSink);
358 private static <K, V> void initIndexStorage(@NotNull FileBasedIndexExtension<K, V> extension,
360 @NotNull IndexConfiguration state,
361 @NotNull IndexVersionRegistrationSink registrationStatusSink)
363 VfsAwareIndexStorage<K, V> storage = null;
364 final ID<K, V> name = extension.getName();
365 boolean contentHashesEnumeratorOk = false;
367 final InputFilter inputFilter = extension.getInputFilter();
368 final Set<FileType> addedTypes;
369 if (inputFilter instanceof FileBasedIndex.FileTypeSpecificInputFilter) {
370 addedTypes = new HashSet<>();
371 ((FileBasedIndex.FileTypeSpecificInputFilter)inputFilter).registerFileTypesUsedForIndexing(type -> {
372 if (type != null) addedTypes.add(type);
379 for (int attempt = 0; attempt < 2; attempt++) {
381 if (VfsAwareMapReduceIndex.hasSnapshotMapping(extension)) {
382 contentHashesEnumeratorOk = SnapshotHashEnumeratorService.getInstance().initialize();
383 if (!contentHashesEnumeratorOk) {
384 throw new IOException("content hash enumerator will be forcibly clean");
388 storage = createIndexStorage(extension);
390 UpdatableIndex<K, V, FileContent> index = createIndex(extension, new TransientChangesIndexStorage<>(storage, name));
392 for (FileBasedIndexInfrastructureExtension infrastructureExtension : FileBasedIndexInfrastructureExtension.EP_NAME.getExtensionList()) {
393 UpdatableIndex<K, V, FileContent> intermediateIndex = infrastructureExtension.combineIndex(extension, index);
394 if (intermediateIndex != null) {
395 index = intermediateIndex;
399 state.registerIndex(name,
401 file -> file instanceof VirtualFileWithId && inputFilter.acceptInput(file) &&
402 !GlobalIndexFilter.isExcludedFromIndexViaFilters(file, name),
403 version + GlobalIndexFilter.getFiltersVersion(name),
407 catch (Exception e) {
408 if (ApplicationManager.getApplication().isUnitTestMode()) {
414 boolean instantiatedStorage = storage != null;
416 if (storage != null) storage.close();
419 catch (Exception ignored) {
422 FileUtil.deleteWithRenaming(IndexInfrastructure.getIndexRootDir(name));
424 if (extension.hasSnapshotMapping() && (!contentHashesEnumeratorOk || instantiatedStorage)) {
425 FileUtil.deleteWithRenaming(IndexInfrastructure.getPersistentIndexRootDir(name)); // todo there is possibility of corruption of storage and content hashes
427 registrationStatusSink.setIndexVersionDiff(name, new IndexingStamp.IndexVersionDiff.CorruptedRebuild(version));
428 IndexingStamp.rewriteVersion(name, version);
434 private static <K, V> VfsAwareIndexStorage<K, V> createIndexStorage(FileBasedIndexExtension<K, V> extension) throws IOException {
435 if (USE_IN_MEMORY_INDEX) {
436 return new InMemoryIndexStorage<>();
438 boolean createSnapshotStorage = VfsAwareMapReduceIndex.hasSnapshotMapping(extension) && extension instanceof SingleEntryFileBasedIndexExtension;
439 return createSnapshotStorage ? new SnapshotSingleValueIndexStorage<>(extension.getCacheSize()) : new VfsAwareMapIndexStorage<>(
440 IndexInfrastructure.getStorageFile(extension.getName()).toPath(),
441 extension.getKeyDescriptor(),
442 extension.getValueExternalizer(),
443 extension.getCacheSize(),
444 extension.keyIsUniqueForIndexedFile(),
445 extension.traceKeyHashToVirtualFileMapping()
450 private static <K, V> UpdatableIndex<K, V, FileContent> createIndex(@NotNull final FileBasedIndexExtension<K, V> extension,
451 @NotNull final TransientChangesIndexStorage<K, V> storage)
452 throws StorageException, IOException {
453 return extension instanceof CustomImplementationFileBasedIndexExtension
454 ? ((CustomImplementationFileBasedIndexExtension<K, V>)extension).createIndexImplementation(extension, storage)
455 : new VfsAwareMapReduceIndex<>(extension, storage);
458 void performShutdown(boolean keepConnection) {
459 RegisteredIndexes registeredIndexes = myRegisteredIndexes;
460 if (registeredIndexes == null || !registeredIndexes.performShutdown()) {
461 return; // already shut down
464 registeredIndexes.waitUntilAllIndicesAreInitialized();
466 if (myShutDownTask != null) {
467 ShutDownTracker.getInstance().unregisterShutdownTask(myShutDownTask);
469 if (myFlushingFuture != null) {
470 myFlushingFuture.cancel(false);
471 myFlushingFuture = null;
475 LOG.info("START INDEX SHUTDOWN");
477 PersistentIndicesConfiguration.saveConfiguration();
479 for (VirtualFile file : getChangedFilesCollector().getAllPossibleFilesToUpdate()) {
480 int fileId = getIdMaskingNonIdBasedFile(file);
481 if (file.isValid()) {
482 dropNontrivialIndexedStates(fileId);
485 removeDataFromIndicesForFile(Math.abs(fileId), file);
488 getChangedFilesCollector().clearFilesToUpdate();
490 IndexingStamp.flushCaches();
492 IndexConfiguration state = getState();
493 for (ID<?, ?> indexId : state.getIndexIDs()) {
495 final UpdatableIndex<?, ?, FileContent> index = state.getIndex(indexId);
496 assert index != null;
497 if (!RebuildStatus.isOk(indexId)) {
498 index.clear(); // if the index was scheduled for rebuild, only clean it
501 } catch (Throwable throwable) {
502 LOG.info("Problem disposing " + indexId, throwable);
506 FileBasedIndexInfrastructureExtension.EP_NAME.extensions().forEach(ex -> ex.shutdown());
507 SnapshotHashEnumeratorService.getInstance().close();
508 if (!keepConnection) {
509 myConnection.disconnect();
512 catch (Throwable e) {
513 LOG.error("Problems during index shutdown", e);
515 LOG.info("END INDEX SHUTDOWN");
519 private void removeDataFromIndicesForFile(int fileId, VirtualFile file) {
520 VirtualFile originalFile = file instanceof DeletedVirtualFileStub ? ((DeletedVirtualFileStub)file).getOriginalFile() : file;
521 final List<ID<?, ?>> states = IndexingStamp.getNontrivialFileIndexedStates(fileId);
523 if (!states.isEmpty()) {
524 ProgressManager.getInstance().executeNonCancelableSection(() -> removeFileDataFromIndices(states, fileId, originalFile));
528 private void removeFileDataFromIndices(@NotNull Collection<? extends ID<?, ?>> affectedIndices, int inputId, VirtualFile file) {
529 // document diff can depend on previous value that will be removed
530 removeTransientFileDataFromIndices(affectedIndices, inputId, file);
532 Throwable unexpectedError = null;
533 for (ID<?, ?> indexId : affectedIndices) {
535 updateSingleIndex(indexId, null, inputId, null);
537 catch (ProcessCanceledException pce) {
540 catch (Throwable e) {
542 if (unexpectedError == null) {
547 IndexingStamp.flushCache(inputId);
549 if (unexpectedError != null) {
550 LOG.error(unexpectedError);
554 private void removeTransientFileDataFromIndices(Collection<? extends ID<?, ?>> indices, int inputId, VirtualFile file) {
555 for (ID<?, ?> indexId : indices) {
556 final UpdatableIndex<?, ?, FileContent> index = myRegisteredIndexes.getState().getIndex(indexId);
558 throw new AssertionError("index '" + indexId.getName() + "' can't be found among registered indexes: " + myRegisteredIndexes.getState().getIndexIDs());
560 index.removeTransientDataForFile(inputId);
563 Document document = myFileDocumentManager.getCachedDocument(file);
564 if (document != null) {
565 myLastIndexedDocStamps.clearForDocument(document);
566 document.putUserData(ourFileContentKey, null);
569 clearUpToDateIndexesForUnsavedOrTransactedDocs();
572 private void flushAllIndices(final long modCount) {
573 if (HeavyProcessLatch.INSTANCE.isRunning()) {
576 IndexingStamp.flushCaches();
577 IndexConfiguration state = getState();
578 for (ID<?, ?> indexId : new ArrayList<>(state.getIndexIDs())) {
579 if (HeavyProcessLatch.INSTANCE.isRunning() || modCount != myLocalModCount.get()) {
580 return; // do not interfere with 'main' jobs
583 final UpdatableIndex<?, ?, FileContent> index = state.getIndex(indexId);
588 catch (Throwable e) {
589 requestRebuild(indexId, e);
593 SnapshotHashEnumeratorService.getInstance().flush();
596 private static final ThreadLocal<Integer> myUpToDateCheckState = new ThreadLocal<>();
598 public static <T,E extends Throwable> T disableUpToDateCheckIn(@NotNull ThrowableComputable<T, E> runnable) throws E {
599 disableUpToDateCheckForCurrentThread();
601 return runnable.compute();
604 enableUpToDateCheckForCurrentThread();
607 private static void disableUpToDateCheckForCurrentThread() {
608 final Integer currentValue = myUpToDateCheckState.get();
609 myUpToDateCheckState.set(currentValue == null ? 1 : currentValue.intValue() + 1);
612 private static void enableUpToDateCheckForCurrentThread() {
613 final Integer currentValue = myUpToDateCheckState.get();
614 if (currentValue != null) {
615 final int newValue = currentValue.intValue() - 1;
617 myUpToDateCheckState.set(newValue);
620 myUpToDateCheckState.remove();
625 static boolean isUpToDateCheckEnabled() {
626 final Integer value = myUpToDateCheckState.get();
627 return value == null || value.intValue() == 0;
630 private final ThreadLocal<Boolean> myReentrancyGuard = ThreadLocal.withInitial(() -> Boolean.FALSE);
633 public <K> boolean ensureUpToDate(@NotNull final ID<K, ?> indexId,
634 @Nullable Project project,
635 @Nullable GlobalSearchScope filter,
636 @Nullable VirtualFile restrictedFile) {
637 ProgressManager.checkCanceled();
638 getChangedFilesCollector().ensureUpToDate();
639 ApplicationManager.getApplication().assertReadAccessAllowed();
641 NoAccessDuringPsiEvents.checkCallContext(indexId);
643 if (!needsFileContentLoading(indexId)) {
644 return true; //indexed eagerly in foreground while building unindexed file list
646 if (filter == GlobalSearchScope.EMPTY_SCOPE) {
649 if (ActionUtil.isDumbMode(project) && getCurrentDumbModeAccessType_NoDumbChecks() == null) {
650 handleDumbMode(project);
653 if (myReentrancyGuard.get().booleanValue()) {
654 //assert false : "ensureUpToDate() is not reentrant!";
657 myReentrancyGuard.set(Boolean.TRUE);
660 if (isUpToDateCheckEnabled()) {
662 if (!RebuildStatus.isOk(indexId)) {
663 if (getCurrentDumbModeAccessType_NoDumbChecks() == null) {
664 throw new ServiceNotReadyException();
668 if (!ActionUtil.isDumbMode(project) || getCurrentDumbModeAccessType_NoDumbChecks() == null) {
669 forceUpdate(project, filter, restrictedFile);
671 if (!areUnsavedDocumentsIndexed(indexId)) { // todo: check scope ?
672 indexUnsavedDocuments(indexId, project, filter, restrictedFile);
675 catch (RuntimeException e) {
676 final Throwable cause = e.getCause();
677 if (cause instanceof StorageException || cause instanceof IOException) {
678 scheduleRebuild(indexId, e);
687 myReentrancyGuard.set(Boolean.FALSE);
692 private boolean areUnsavedDocumentsIndexed(@NotNull ID<?, ?> indexId) {
693 return myUpToDateIndicesForUnsavedOrTransactedDocuments.contains(indexId);
696 private static void handleDumbMode(@Nullable Project project) throws IndexNotReadyException {
697 ProgressManager.checkCanceled();
698 throw IndexNotReadyException.create(project == null ? null : DumbServiceImpl.getInstance(project).getDumbModeStartTrace());
701 private static final Key<SoftReference<ProjectIndexableFilesFilter>> ourProjectFilesSetKey = Key.create("projectFiles");
704 public void cleanupForNextTest() {
705 getChangedFilesCollector().ensureUpToDate();
707 myTransactionMap = SmartFMap.emptyMap();
708 IndexConfiguration state = getState();
709 for (ID<?, ?> indexId : state.getIndexIDs()) {
710 final UpdatableIndex<?, ?, FileContent> index = state.getIndex(indexId);
711 assert index != null;
712 index.cleanupForNextTest();
717 public ChangedFilesCollector getChangedFilesCollector() {
718 return myChangedFilesCollector.getValue();
721 void incrementFilesModCount() {
722 myFilesModCount.incrementAndGet();
725 void filesUpdateStarted(Project project) {
726 getChangedFilesCollector().ensureUpToDate();
727 myProjectsBeingUpdated.add(project);
728 incrementFilesModCount();
731 void filesUpdateFinished(@NotNull Project project) {
732 myProjectsBeingUpdated.remove(project);
733 incrementFilesModCount();
736 private final Lock myCalcIndexableFilesLock = new ReentrantLock();
740 public ProjectIndexableFilesFilter projectIndexableFiles(@Nullable Project project) {
741 if (project == null || project.isDefault() || getChangedFilesCollector().isUpdateInProgress()) return null;
742 if (myProjectsBeingUpdated.contains(project) || !UnindexedFilesUpdater.isProjectContentFullyScanned(project)) return null;
744 SoftReference<ProjectIndexableFilesFilter> reference = project.getUserData(ourProjectFilesSetKey);
745 ProjectIndexableFilesFilter data = com.intellij.reference.SoftReference.dereference(reference);
746 int currentFileModCount = myFilesModCount.get();
747 if (data != null && data.getModificationCount() == currentFileModCount) return data;
749 if (myCalcIndexableFilesLock.tryLock()) { // make best effort for calculating filter
751 reference = project.getUserData(ourProjectFilesSetKey);
752 data = com.intellij.reference.SoftReference.dereference(reference);
754 if (data.getModificationCount() == currentFileModCount) {
757 } else if (!isUpToDateCheckEnabled()) {
761 long start = System.currentTimeMillis();
763 IntArrayList fileSet = new IntArrayList();
764 iterateIndexableFiles(fileOrDir -> {
765 if (fileOrDir instanceof VirtualFileWithId) {
766 fileSet.add(((VirtualFileWithId)fileOrDir).getId());
770 ProjectIndexableFilesFilter filter = new ProjectIndexableFilesFilter(fileSet, currentFileModCount);
771 project.putUserData(ourProjectFilesSetKey, new SoftReference<>(filter));
773 long finish = System.currentTimeMillis();
774 LOG.debug(fileSet.size() + " files iterated in " + (finish - start) + " ms");
779 myCalcIndexableFilesLock.unlock();
782 return null; // ok, no filtering
786 public static Throwable getCauseToRebuildIndex(@NotNull RuntimeException e) {
787 if (ApplicationManager.getApplication().isUnitTestMode()) {
788 // avoid rebuilding index in tests since we do it synchronously in requestRebuild and we can have readAction at hand
791 if (e instanceof ProcessCanceledException) {
794 if (e instanceof MapReduceIndex.MapInputException) {
795 // If exception has happened on input mapping (DataIndexer.map),
796 // it is handled as the indexer exception and must not lead to index rebuild.
799 if (e instanceof IndexOutOfBoundsException) return e; // something wrong with direct byte buffer
800 Throwable cause = e.getCause();
801 if (cause instanceof StorageException
802 || cause instanceof IOException
803 || cause instanceof IllegalArgumentException
810 private static void scheduleIndexRebuild(String reason) {
811 LOG.info("scheduleIndexRebuild, reason: " + reason);
812 for (Project project : ProjectManager.getInstance().getOpenProjects()) {
813 DumbService.getInstance(project).queueTask(new UnindexedFilesUpdater(project));
817 void clearIndicesIfNecessary() {
818 waitUntilIndicesAreInitialized();
819 for (ID<?, ?> indexId : getState().getIndexIDs()) {
821 RebuildStatus.clearIndexIfNecessary(indexId, getIndex(indexId)::clear);
823 catch (StorageException e) {
824 requestRebuild(indexId);
830 void clearIndex(@NotNull final ID<?, ?> indexId) throws StorageException {
831 advanceIndexVersion(indexId);
833 final UpdatableIndex<?, ?, FileContent> index = myRegisteredIndexes.getState().getIndex(indexId);
834 assert index != null : "Index with key " + indexId + " not found or not registered properly";
838 private void advanceIndexVersion(ID<?, ?> indexId) {
840 IndexingStamp.rewriteVersion(indexId, myRegisteredIndexes.getState().getIndexVersion(indexId));
842 catch (IOException e) {
848 private Set<Document> getUnsavedDocuments() {
849 Document[] documents = myFileDocumentManager.getUnsavedDocuments();
850 if (documents.length == 0) return Collections.emptySet();
851 if (documents.length == 1) return Collections.singleton(documents[0]);
852 return ContainerUtil.set(documents);
856 private Set<Document> getTransactedDocuments() {
857 return myTransactionMap.keySet();
860 private void indexUnsavedDocuments(@NotNull final ID<?, ?> indexId,
861 @Nullable Project project,
862 final GlobalSearchScope filter,
863 final VirtualFile restrictedFile) {
864 if (myUpToDateIndicesForUnsavedOrTransactedDocuments.contains(indexId)) {
865 return; // no need to index unsaved docs // todo: check scope ?
868 Collection<Document> documents = getUnsavedDocuments();
869 Set<Document> transactedDocuments = getTransactedDocuments();
870 if (documents.isEmpty()) {
871 documents = transactedDocuments;
873 else if (!transactedDocuments.isEmpty()) {
874 documents = new HashSet<>(documents);
875 documents.addAll(transactedDocuments);
877 Document[] uncommittedDocuments = project != null ? PsiDocumentManager.getInstance(project).getUncommittedDocuments() : Document.EMPTY_ARRAY;
878 if (uncommittedDocuments.length > 0) {
879 List<Document> uncommittedDocumentsCollection = Arrays.asList(uncommittedDocuments);
880 if (documents.isEmpty()) documents = uncommittedDocumentsCollection;
882 if (!(documents instanceof HashSet)) documents = new HashSet<>(documents);
884 documents.addAll(uncommittedDocumentsCollection);
888 if (!documents.isEmpty()) {
889 Collection<Document> documentsToProcessForProject = ContainerUtil.filter(documents,
890 document -> belongsToScope(myFileDocumentManager.getFile(document), restrictedFile, filter));
892 if (!documentsToProcessForProject.isEmpty()) {
893 UpdateTask<Document> task = myRegisteredIndexes.getUnsavedDataUpdateTask(indexId);
894 assert task != null : "Task for unsaved data indexing was not initialized for index " + indexId;
896 if(myStorageBufferingHandler.runUpdate(true, () -> task.processAll(documentsToProcessForProject, project)) &&
897 documentsToProcessForProject.size() == documents.size() &&
898 !hasActiveTransactions()
900 ProgressManager.checkCanceled();
901 myUpToDateIndicesForUnsavedOrTransactedDocuments.add(indexId);
907 private boolean hasActiveTransactions() {
908 return !myTransactionMap.isEmpty();
912 private static final Key<WeakReference<FileContentImpl>> ourFileContentKey = Key.create("unsaved.document.index.content");
914 // returns false if doc was not indexed because it is already up to date
915 // return true if document was indexed
916 // caller is responsible to ensure no concurrent same document processing
917 void indexUnsavedDocument(@NotNull final Document document, @NotNull final ID<?, ?> requestedIndexId, final Project project,
918 @NotNull final VirtualFile vFile) {
919 final PsiFile dominantContentFile = project == null ? null : findLatestKnownPsiForUncomittedDocument(document, project);
921 final DocumentContent content;
922 if (dominantContentFile != null && dominantContentFile.getViewProvider().getModificationStamp() != document.getModificationStamp()) {
923 content = new PsiContent(document, dominantContentFile);
926 content = new AuthenticContent(document);
929 final long currentDocStamp = PsiDocumentManager.getInstance(project).getLastCommittedStamp(document);
931 final long previousDocStamp = myLastIndexedDocStamps.get(document, requestedIndexId);
932 if (previousDocStamp == currentDocStamp) return;
934 final CharSequence contentText = content.getText();
935 getFileTypeManager().freezeFileTypeTemporarilyIn(vFile, () -> {
936 if (getAffectedIndexCandidates(vFile).contains(requestedIndexId) &&
937 getInputFilter(requestedIndexId).acceptInput(vFile)) {
938 final int inputId = Math.abs(getFileId(vFile));
940 if (!isTooLarge(vFile, (long)contentText.length())) {
941 // Reasonably attempt to use same file content when calculating indices as we can evaluate them several at once and store in file content
942 WeakReference<FileContentImpl> previousContentRef = document.getUserData(ourFileContentKey);
943 FileContentImpl previousContent = com.intellij.reference.SoftReference.dereference(previousContentRef);
944 final FileContentImpl newFc;
945 if (previousContent != null && previousContent.getStamp() == currentDocStamp) {
946 newFc = previousContent;
949 newFc = new FileContentImpl(vFile, contentText, currentDocStamp);
950 document.putUserData(ourFileContentKey, new WeakReference<>(newFc));
953 initFileContent(newFc, project, dominantContentFile);
954 newFc.ensureThreadSafeLighterAST();
956 if (content instanceof AuthenticContent) {
957 newFc.putUserData(PlatformIdTableBuilding.EDITOR_HIGHLIGHTER,
958 EditorHighlighterCache.getEditorHighlighterForCachesBuilding(document));
961 markFileIndexed(vFile);
963 getIndex(requestedIndexId).mapInputAndPrepareUpdate(inputId, newFc).compute();
966 unmarkBeingIndexed();
967 cleanFileContent(newFc, dominantContentFile);
970 else { // effectively wipe the data from the indices
971 getIndex(requestedIndexId).mapInputAndPrepareUpdate(inputId, null).compute();
975 long previousState = myLastIndexedDocStamps.set(document, requestedIndexId, currentDocStamp);
976 assert previousState == previousDocStamp;
982 public <K, V> Map<K, V> getFileData(@NotNull ID<K, V> id, @NotNull VirtualFile virtualFile, @NotNull Project project) {
983 if (ModelBranch.getFileBranch(virtualFile) != null) {
984 return getInMemoryData(id, virtualFile, project);
987 return super.getFileData(id, virtualFile, project);
990 @SuppressWarnings({"unchecked", "rawtypes"})
992 private <K, V> Map<K, V> getInMemoryData(@NotNull ID<K, V> id, @NotNull VirtualFile virtualFile, @NotNull Project project) {
993 PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
994 if (psiFile != null) {
995 Map<ID, Map> indexValues = CachedValuesManager.getCachedValue(psiFile, () -> {
997 FileContentImpl fc = psiFile instanceof PsiBinaryFile ? FileContentImpl.createByFile(virtualFile, null)
998 : new FileContentImpl(virtualFile, psiFile.getViewProvider().getContents(), 0);
999 initFileContent(fc, project, psiFile);
1000 Map<ID, Map> result = FactoryMap.create(key -> getIndex(key).getExtension().getIndexer().map(fc));
1001 return CachedValueProvider.Result.createSingleDependency(result, psiFile);
1003 catch (IOException e) {
1004 throw new RuntimeException(e);
1007 return indexValues.get(id);
1009 return Collections.emptyMap();
1013 private final StorageBufferingHandler myStorageBufferingHandler = new StorageBufferingHandler() {
1016 protected Stream<UpdatableIndex<?, ?, ?>> getIndexes() {
1017 IndexConfiguration state = getState();
1018 return state.getIndexIDs().stream().map(id -> state.getIndex(id));
1023 public void runCleanupAction(@NotNull Runnable cleanupAction) {
1024 Computable<Boolean> updateComputable = () -> {
1025 cleanupAction.run();
1028 myStorageBufferingHandler.runUpdate(false, updateComputable);
1029 myStorageBufferingHandler.runUpdate(true, updateComputable);
1032 void cleanupMemoryStorage(boolean skipContentDependentIndexes) {
1033 myLastIndexedDocStamps.clear();
1034 if (myRegisteredIndexes == null) {
1035 // unsaved doc is dropped while plugin load/unload-ing
1038 IndexConfiguration state = myRegisteredIndexes.getState();
1039 if (state == null) {
1040 // avoid waiting for end of indices initialization (IDEA-173382)
1041 // in memory content will appear on indexing (in read action) and here is event dispatch (write context)
1044 for (ID<?, ?> indexId : state.getIndexIDs()) {
1045 if (skipContentDependentIndexes && myRegisteredIndexes.isContentDependentIndex(indexId)) continue;
1046 final UpdatableIndex<?, ?, FileContent> index = state.getIndex(indexId);
1047 assert index != null;
1048 index.cleanupMemoryStorage();
1053 public void requestRebuild(@NotNull final ID<?, ?> indexId, final @NotNull Throwable throwable) {
1054 if (!myRegisteredIndexes.isExtensionsDataLoaded()) {
1055 IndexInfrastructure.submitGenesisTask(() -> {
1056 waitUntilIndicesAreInitialized(); // should be always true here since the genesis pool is sequential
1057 doRequestRebuild(indexId, throwable);
1062 doRequestRebuild(indexId, throwable);
1066 private void doRequestRebuild(@NotNull ID<?, ?> indexId, Throwable throwable) {
1067 cleanupProcessedFlag();
1068 if (!myRegisteredIndexes.isExtensionsDataLoaded()) reportUnexpectedAsyncInitState();
1070 if (RebuildStatus.requestRebuild(indexId)) {
1071 String message = "Rebuild requested for index " + indexId;
1072 Application app = ApplicationManager.getApplication();
1073 if (app.isUnitTestMode() && app.isReadAccessAllowed() && !app.isDispatchThread()) {
1074 // shouldn't happen in tests in general; so fail early with the exception that caused index to be rebuilt.
1075 // otherwise reindexing will fail anyway later, but with a much more cryptic assertion
1076 LOG.error(message, throwable);
1079 LOG.info(message, throwable);
1082 cleanupProcessedFlag();
1084 if (!myRegisteredIndexes.isInitialized()) return;
1085 advanceIndexVersion(indexId);
1087 Runnable rebuildRunnable = () -> scheduleIndexRebuild("checkRebuild");
1089 if (myIsUnitTestMode) {
1090 rebuildRunnable.run();
1093 // we do invoke later since we can have read lock acquired
1094 AppUIExecutor.onWriteThread().later().expireWith(app).submit(rebuildRunnable);
1099 private static void reportUnexpectedAsyncInitState() {
1100 LOG.error("Unexpected async indices initialization problem");
1104 public <K, V> UpdatableIndex<K, V, FileContent> getIndex(ID<K, V> indexId) {
1105 return getState().getIndex(indexId);
1108 private InputFilter getInputFilter(@NotNull ID<?, ?> indexId) {
1109 if (!myRegisteredIndexes.isInitialized()) {
1110 // 1. early vfs event that needs invalidation
1111 // 2. pushers that do synchronous indexing for contentless indices
1112 waitUntilIndicesAreInitialized();
1115 return getState().getInputFilter(indexId);
1119 Collection<VirtualFile> getFilesToUpdate(final Project project) {
1120 return ContainerUtil.filter(getChangedFilesCollector().getAllFilesToUpdate(), filesToBeIndexedForProjectCondition(project)::test);
1124 private Predicate<VirtualFile> filesToBeIndexedForProjectCondition(Project project) {
1125 return virtualFile -> {
1126 if (!virtualFile.isValid()) {
1130 for (IndexableFileSet set : myIndexableSets) {
1131 final Project proj = myIndexableSetToProjectMap.get(set);
1132 if (proj != null && !proj.equals(project)) {
1133 continue; // skip this set as associated with a different project
1135 if (ReadAction.compute(() -> set.isInSet(virtualFile))) {
1143 public boolean isFileUpToDate(VirtualFile file) {
1144 return !getChangedFilesCollector().isScheduledForUpdate(file);
1147 // caller is responsible to ensure no concurrent same document processing
1148 private void processRefreshedFile(@Nullable Project project, @NotNull final CachedFileContent fileContent) {
1149 // ProcessCanceledException will cause re-adding the file to processing list
1150 final VirtualFile file = fileContent.getVirtualFile();
1151 if (getChangedFilesCollector().isScheduledForUpdate(file)) {
1152 indexFileContent(project, fileContent);
1158 public FileIndexingStatistics indexFileContent(@Nullable Project project, @NotNull CachedFileContent content) {
1159 ProgressManager.checkCanceled();
1160 VirtualFile file = content.getVirtualFile();
1161 final int fileId = Math.abs(getIdMaskingNonIdBasedFile(file));
1163 FileIndexingResult indexingResult;
1165 // if file was scheduled for update due to vfs events then it is present in myFilesToUpdate
1166 // in this case we consider that current indexing (out of roots backed CacheUpdater) will cover its content
1167 if (file.isValid() && content.getTimeStamp() != file.getTimeStamp()) {
1168 content = new CachedFileContent(file);
1170 if (!file.isValid() || isTooLarge(file)) {
1171 ProgressManager.checkCanceled();
1172 removeDataFromIndicesForFile(fileId, file);
1173 if (file instanceof DeletedVirtualFileStub && ((DeletedVirtualFileStub)file).isResurrected()) {
1174 CachedFileContent resurrectedFileContent = new CachedFileContent(((DeletedVirtualFileStub)file).getOriginalFile());
1175 indexingResult = doIndexFileContent(project, resurrectedFileContent);
1177 indexingResult = new FileIndexingResult(true, Collections.emptyMap(), file.getFileType());
1181 indexingResult = doIndexFileContent(project, content);
1184 if (indexingResult.setIndexedStatus && file instanceof VirtualFileSystemEntry) {
1185 ((VirtualFileSystemEntry)file).setFileIndexed(true);
1187 if (VfsEventsMerger.LOG != null) {
1188 VfsEventsMerger.LOG.info("File " + file + " has been indexed for indexes " + indexingResult.timesPerIndexer.keySet());
1190 getChangedFilesCollector().removeFileIdFromFilesScheduledForUpdate(fileId);
1191 // Indexing time takes only input data mapping time into account.
1192 long indexingTime = indexingResult.timesPerIndexer.values().stream().mapToLong(e -> e).sum();
1193 return new FileIndexingStatistics(indexingTime,
1194 indexingResult.fileType,
1195 indexingResult.timesPerIndexer);
1198 IndexingStamp.flushCache(fileId);
1202 private static final class FileIndexingResult {
1203 public final boolean setIndexedStatus;
1204 public final Map<ID<?, ?>, Long> timesPerIndexer;
1205 public final FileType fileType;
1207 private FileIndexingResult(boolean setIndexedStatus,
1208 @NotNull Map<ID<?, ?>, Long> timesPerIndexer,
1209 @NotNull FileType type) {
1210 this.setIndexedStatus = setIndexedStatus;
1211 this.timesPerIndexer = timesPerIndexer;
1216 private static final class SingleIndexUpdateStats {
1217 public final long mapInputTime;
1219 private SingleIndexUpdateStats(long mapInputTime) {
1220 this.mapInputTime = mapInputTime;
1225 private FileBasedIndexImpl.FileIndexingResult doIndexFileContent(@Nullable Project project, @NotNull CachedFileContent content) {
1226 ProgressManager.checkCanceled();
1227 final VirtualFile file = content.getVirtualFile();
1228 Ref<Boolean> setIndexedStatus = Ref.create(Boolean.TRUE);
1229 Map<ID<?, ?>, Long> perIndexerTimes = new HashMap<>();
1230 Ref<FileType> fileTypeRef = Ref.create();
1232 getFileTypeManager().freezeFileTypeTemporarilyIn(file, () -> {
1233 ProgressManager.checkCanceled();
1235 FileContentImpl fc = null;
1236 PsiFile psiFile = null;
1238 int inputId = Math.abs(getFileId(file));
1239 Set<ID<?, ?>> currentIndexedStates = new HashSet<>(IndexingStamp.getNontrivialFileIndexedStates(inputId));
1240 List<ID<?, ?>> affectedIndexCandidates = getAffectedIndexCandidates(file);
1241 //noinspection ForLoopReplaceableByForEach
1242 for (int i = 0, size = affectedIndexCandidates.size(); i < size; ++i) {
1244 ProgressManager.checkCanceled();
1247 fc = new LazyFileContentImpl(file, () -> getBytesOrNull(content));
1249 ProgressManager.checkCanceled();
1251 psiFile = content.getUserData(IndexingDataKeys.PSI_FILE);
1252 initFileContent(fc, project == null ? ProjectUtil.guessProjectForFile(file) : project, psiFile);
1254 fileTypeRef.set(fc.getFileType());
1256 ProgressManager.checkCanceled();
1259 final ID<?, ?> indexId = affectedIndexCandidates.get(i);
1260 if (getInputFilter(indexId).acceptInput(file) && getIndexingState(fc, indexId).updateRequired()) {
1261 ProgressManager.checkCanceled();
1262 SingleIndexUpdateStats updateStats = updateSingleIndex(indexId, file, inputId, fc);
1263 if (updateStats == null) {
1264 setIndexedStatus.set(Boolean.FALSE);
1266 perIndexerTimes.put(indexId, updateStats.mapInputTime);
1268 currentIndexedStates.remove(indexId);
1271 catch (ProcessCanceledException e) {
1272 cleanFileContent(fc, psiFile);
1277 if (psiFile != null) {
1278 psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1281 boolean shouldClearAllIndexedStates = fc == null;
1282 for (ID<?, ?> indexId : currentIndexedStates) {
1283 ProgressManager.checkCanceled();
1284 if (shouldClearAllIndexedStates || getIndex(indexId).getIndexingStateForFile(inputId, fc).updateRequired()) {
1285 ProgressManager.checkCanceled();
1286 SingleIndexUpdateStats updateStats = updateSingleIndex(indexId, file, inputId, null);
1287 if (updateStats == null) {
1288 setIndexedStatus.set(Boolean.FALSE);
1290 perIndexerTimes.put(indexId, updateStats.mapInputTime);
1295 fileTypeRef.set(fc != null ? fc.getFileType() : file.getFileType());
1298 file.putUserData(IndexingDataKeys.REBUILD_REQUESTED, null);
1299 return new FileIndexingResult(setIndexedStatus.get(), perIndexerTimes, fileTypeRef.get());
1302 private static byte @NotNull[] getBytesOrNull(@NotNull CachedFileContent content) {
1304 return content.getBytes();
1305 } catch (IOException e) {
1306 return ArrayUtilRt.EMPTY_BYTE_ARRAY;
1311 public boolean isIndexingCandidate(@NotNull VirtualFile file, @NotNull ID<?, ?> indexId) {
1312 return !isTooLarge(file) && getAffectedIndexCandidates(file).contains(indexId);
1316 List<ID<?, ?>> getAffectedIndexCandidates(@NotNull VirtualFile file) {
1317 if (file.isDirectory()) {
1318 return isProjectOrWorkspaceFile(file, null) ? Collections.emptyList() : myRegisteredIndexes.getIndicesForDirectories();
1320 FileType fileType = file.getFileType();
1321 if(isProjectOrWorkspaceFile(file, fileType)) return Collections.emptyList();
1323 return getState().getFileTypesForIndex(fileType);
1326 private static void cleanFileContent(FileContentImpl fc, PsiFile psiFile) {
1327 if (fc == null) return;
1328 if (psiFile != null) psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1329 fc.putUserData(IndexingDataKeys.PSI_FILE, null);
1332 private static void initFileContent(@NotNull FileContentImpl fc, Project project, PsiFile psiFile) {
1333 if (psiFile != null) {
1334 psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true);
1335 fc.putUserData(IndexingDataKeys.PSI_FILE, psiFile);
1338 fc.setProject(project);
1341 @Nullable("null in case index update is not necessary or the update has failed")
1342 SingleIndexUpdateStats updateSingleIndex(@NotNull ID<?, ?> indexId, @Nullable VirtualFile file, int inputId, @Nullable FileContent currentFC) {
1343 if (!myRegisteredIndexes.isExtensionsDataLoaded()) reportUnexpectedAsyncInitState();
1344 if (!RebuildStatus.isOk(indexId) && !myIsUnitTestMode) {
1345 return null; // the index is scheduled for rebuild, no need to update
1347 myLocalModCount.incrementAndGet();
1349 final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
1350 assert index != null;
1352 markFileIndexed(file);
1354 Computable<Boolean> storageUpdate;
1355 long mapInputTime = System.nanoTime();
1357 // Propagate MapReduceIndex.MapInputException and ProcessCancelledException happening on input mapping.
1358 storageUpdate = index.mapInputAndPrepareUpdate(inputId, currentFC);
1360 mapInputTime = System.nanoTime() - mapInputTime;
1362 if (myStorageBufferingHandler.runUpdate(false, storageUpdate)) {
1363 ConcurrencyUtil.withLock(myReadLock, () -> {
1364 if (currentFC != null) {
1365 if (!isMock(currentFC.getFile())) {
1366 index.setIndexedStateForFile(inputId, currentFC);
1370 index.resetIndexedStateForFile(inputId);
1374 return new SingleIndexUpdateStats(mapInputTime);
1376 catch (RuntimeException exception) {
1377 Throwable causeToRebuildIndex = getCauseToRebuildIndex(exception);
1378 if (causeToRebuildIndex != null) {
1379 requestRebuild(indexId, exception);
1385 unmarkBeingIndexed();
1389 private static void markFileIndexed(@Nullable VirtualFile file) {
1390 if (ourIndexedFile.get() != null || ourFileToBeIndexed.get() != null) {
1391 throw new AssertionError("Reentrant indexing");
1393 ourIndexedFile.set(file);
1396 private static void unmarkBeingIndexed() {
1397 ourIndexedFile.remove();
1401 public VirtualFile getFileBeingCurrentlyIndexed() {
1402 return ourIndexedFile.get();
1405 private class VirtualFileUpdateTask extends UpdateTask<VirtualFile> {
1407 void doProcess(VirtualFile item, Project project) {
1408 processRefreshedFile(project, new CachedFileContent(item));
1412 private final VirtualFileUpdateTask myForceUpdateTask = new VirtualFileUpdateTask();
1413 private volatile long myLastOtherProjectInclusionStamp;
1415 private void forceUpdate(@Nullable Project project, @Nullable final GlobalSearchScope filter, @Nullable final VirtualFile restrictedTo) {
1416 Collection<VirtualFile> allFilesToUpdate = getChangedFilesCollector().getAllFilesToUpdate();
1418 if (!allFilesToUpdate.isEmpty()) {
1419 boolean includeFilesFromOtherProjects = restrictedTo == null && System.currentTimeMillis() - myLastOtherProjectInclusionStamp > 100;
1420 List<VirtualFile> virtualFilesToBeUpdatedForProject = ContainerUtil.filter(
1422 new ProjectFilesCondition(projectIndexableFiles(project), filter, restrictedTo,
1423 includeFilesFromOtherProjects)
1426 if (!virtualFilesToBeUpdatedForProject.isEmpty()) {
1427 myForceUpdateTask.processAll(virtualFilesToBeUpdatedForProject, project);
1429 if (includeFilesFromOtherProjects) {
1430 myLastOtherProjectInclusionStamp = System.currentTimeMillis();
1435 public boolean needsFileContentLoading(@NotNull ID<?, ?> indexId) {
1436 return myRegisteredIndexes.isContentDependentIndex(indexId);
1439 @Nullable IndexableFileSet getIndexableSetForFile(VirtualFile file) {
1440 for (IndexableFileSet set : myIndexableSets) {
1441 if (set.isInSet(file)) {
1448 @NotNull List<IndexableFileSet> getIndexableSets() {
1449 return myIndexableSets;
1453 public void dropNontrivialIndexedStates(int inputId) {
1454 for (ID<?, ?> state : IndexingStamp.getNontrivialFileIndexedStates(inputId)) {
1455 getIndex(state).resetIndexedStateForFile(inputId);
1459 void doTransientStateChangeForFile(int fileId, @NotNull VirtualFile file) {
1460 waitUntilIndicesAreInitialized();
1461 if (!clearUpToDateStateForPsiIndicesOfUnsavedDocuments(file, IndexingStamp.getNontrivialFileIndexedStates(fileId))) {
1462 // change in persistent file
1463 clearUpToDateStateForPsiIndicesOfVirtualFile(file);
1467 void doInvalidateIndicesForFile(int fileId, @NotNull VirtualFile file, boolean contentChanged) {
1468 waitUntilIndicesAreInitialized();
1469 cleanProcessedFlag(file);
1471 List<ID<?, ?>> nontrivialFileIndexedStates = IndexingStamp.getNontrivialFileIndexedStates(fileId);
1472 Collection<ID<?, ?>> fileIndexedStatesToUpdate = ContainerUtil.intersection(nontrivialFileIndexedStates, myRegisteredIndexes.getRequiringContentIndices());
1474 // transient index value can depend on disk value because former is diff to latter
1475 // it doesn't matter content hanged or not: indices might depend on file name too
1476 removeTransientFileDataFromIndices(nontrivialFileIndexedStates, fileId, file);
1478 if (contentChanged) {
1479 // only mark the file as outdated, reindex will be done lazily
1480 if (!fileIndexedStatesToUpdate.isEmpty()) {
1481 //noinspection ForLoopReplaceableByForEach
1482 for (int i = 0, size = nontrivialFileIndexedStates.size(); i < size; ++i) {
1483 final ID<?, ?> indexId = nontrivialFileIndexedStates.get(i);
1484 if (needsFileContentLoading(indexId)) {
1485 getIndex(indexId).resetIndexedStateForFile(fileId);
1489 // the file is for sure not a dir and it was previously indexed by at least one index
1490 if (file.isValid()) {
1491 if (!isTooLarge(file)) {
1492 getChangedFilesCollector().scheduleForUpdate(file);
1494 else getChangedFilesCollector().scheduleForUpdate(new DeletedVirtualFileStub((VirtualFileWithId)file));
1497 LOG.info("Unexpected state in update:" + file);
1501 else { // file was removed
1502 for (ID<?, ?> indexId : nontrivialFileIndexedStates) {
1503 if (!myRegisteredIndexes.isContentDependentIndex(indexId)) {
1504 updateSingleIndex(indexId, null, fileId, null);
1507 if (!fileIndexedStatesToUpdate.isEmpty()) {
1508 // its data should be (lazily) wiped for every index
1509 getChangedFilesCollector().scheduleForUpdate(new DeletedVirtualFileStub((VirtualFileWithId)file));
1512 getChangedFilesCollector().removeScheduledFileFromUpdate(file); // no need to update it anymore
1517 void scheduleFileForIndexing(int fileId, @NotNull VirtualFile file, boolean contentChange) {
1518 final List<IndexableFilesFilter> filters = IndexableFilesFilter.EP_NAME.getExtensionList();
1519 if (!filters.isEmpty() && !ContainerUtil.exists(filters, e -> e.shouldIndex(file))) return;
1521 // handle 'content-less' indices separately
1522 boolean fileIsDirectory = file.isDirectory();
1524 if (!contentChange) {
1525 FileContent fileContent = null;
1526 for (ID<?, ?> indexId : getContentLessIndexes(fileIsDirectory)) {
1527 if (getInputFilter(indexId).acceptInput(file)) {
1528 if (fileContent == null) {
1529 fileContent = new IndexedFileWrapper(new IndexedFileImpl(file, null));
1531 updateSingleIndex(indexId, file, fileId, fileContent);
1535 // For 'normal indices' schedule the file for update and reset stamps for all affected indices (there
1536 // can be client that used indices between before and after events, in such case indices are up to date due to force update
1537 // with old content)
1538 if (!fileIsDirectory) {
1539 if (!file.isValid() || isTooLarge(file)) {
1540 // large file might be scheduled for update in before event when its size was not large
1541 getChangedFilesCollector().removeScheduledFileFromUpdate(file);
1544 ourFileToBeIndexed.set(file);
1546 getFileTypeManager().freezeFileTypeTemporarilyIn(file, () -> {
1547 final List<ID<?, ?>> candidates = getAffectedIndexCandidates(file);
1549 boolean scheduleForUpdate = false;
1551 //noinspection ForLoopReplaceableByForEach
1552 for (int i = 0, size = candidates.size(); i < size; ++i) {
1553 final ID<?, ?> indexId = candidates.get(i);
1554 if (needsFileContentLoading(indexId) && getInputFilter(indexId).acceptInput(file)) {
1555 getIndex(indexId).resetIndexedStateForFile(fileId);
1556 scheduleForUpdate = true;
1560 if (scheduleForUpdate) {
1561 IndexingStamp.flushCache(fileId);
1562 getChangedFilesCollector().scheduleForUpdate(file);
1564 else if (file instanceof VirtualFileSystemEntry) {
1565 ((VirtualFileSystemEntry)file).setFileIndexed(true);
1569 ourFileToBeIndexed.remove();
1576 Collection<ID<?, ?>> getContentLessIndexes(boolean isDirectory) {
1577 return isDirectory ? myRegisteredIndexes.getIndicesForDirectories() : myRegisteredIndexes.getNotRequiringContentIndices();
1581 public Collection<ID<?, ?>> getContentDependentIndexes() {
1582 return myRegisteredIndexes.getRequiringContentIndices();
1585 static FileTypeManagerImpl getFileTypeManager() {
1586 return (FileTypeManagerImpl)FileTypeManager.getInstance();
1589 private boolean clearUpToDateStateForPsiIndicesOfUnsavedDocuments(@NotNull VirtualFile file, Collection<? extends ID<?, ?>> affectedIndices) {
1590 clearUpToDateIndexesForUnsavedOrTransactedDocs();
1592 Document document = myFileDocumentManager.getCachedDocument(file);
1594 if (document != null && myFileDocumentManager.isDocumentUnsaved(document)) { // will be reindexed in indexUnsavedDocuments
1595 myLastIndexedDocStamps.clearForDocument(document); // Q: non psi indices
1596 document.putUserData(ourFileContentKey, null);
1601 removeTransientFileDataFromIndices(ContainerUtil.intersection(affectedIndices, myRegisteredIndexes.getRequiringContentIndices()), getFileId(file), file);
1605 void clearUpToDateIndexesForUnsavedOrTransactedDocs() {
1606 if (!myUpToDateIndicesForUnsavedOrTransactedDocuments.isEmpty()) {
1607 myUpToDateIndicesForUnsavedOrTransactedDocuments.clear();
1611 static int getIdMaskingNonIdBasedFile(@NotNull VirtualFile file) {
1612 return file instanceof VirtualFileWithId ?((VirtualFileWithId)file).getId() : IndexingStamp.INVALID_FILE_ID;
1615 FileIndexingState shouldIndexFile(@NotNull IndexedFile file, @NotNull ID<?, ?> indexId) {
1616 if (!getInputFilter(indexId).acceptInput(file.getFile())) {
1617 return getIndexingState(file, indexId) == FileIndexingState.NOT_INDEXED
1618 ? FileIndexingState.UP_TO_DATE
1619 : FileIndexingState.OUT_DATED;
1621 return getIndexingState(file, indexId);
1625 private FileIndexingState getIndexingState(@NotNull IndexedFile file, @NotNull ID<?, ?> indexId) {
1626 VirtualFile virtualFile = file.getFile();
1627 if (isMock(virtualFile)) return FileIndexingState.NOT_INDEXED;
1628 return getIndex(indexId).getIndexingStateForFile(((NewVirtualFile)virtualFile).getId(), file);
1631 static boolean isMock(final VirtualFile file) {
1632 return !(file instanceof NewVirtualFile);
1635 public boolean isTooLarge(@NotNull VirtualFile file) {
1636 return isTooLarge(file, null);
1639 public boolean isTooLarge(@NotNull VirtualFile file,
1640 @Nullable("if content size should be retrieved from a file") Long contentSize) {
1641 return isTooLarge(file, contentSize, myRegisteredIndexes.getNoLimitCheckFileTypes());
1645 public static boolean isTooLarge(@NotNull VirtualFile file,
1646 @Nullable("if content size should be retrieved from a file") Long contentSize,
1647 @NotNull Set<FileType> noLimitFileTypes) {
1648 if (SingleRootFileViewProvider.isTooLargeForIntelligence(file, contentSize)) {
1649 return !noLimitFileTypes.contains(file.getFileType()) || SingleRootFileViewProvider.isTooLargeForContentLoading(file, contentSize);
1655 public void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project) {
1656 myIndexableSets.add(set);
1657 myIndexableSetToProjectMap.put(set, project);
1658 if (project != null) {
1659 ((PsiManagerImpl)PsiManager.getInstance(project)).addTreeChangePreprocessor(event -> {
1660 if (event.isGenericChange() &&
1661 event.getCode() == PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED) {
1662 PsiFile file = event.getFile();
1665 VirtualFile virtualFile = file.getVirtualFile();
1666 if (virtualFile instanceof VirtualFileWithId && !isMock(virtualFile)) {
1667 getChangedFilesCollector().getEventMerger().recordTransientStateChangeEvent(virtualFile);
1675 private void clearUpToDateStateForPsiIndicesOfVirtualFile(VirtualFile virtualFile) {
1676 if (virtualFile instanceof VirtualFileWithId) {
1677 int fileId = ((VirtualFileWithId)virtualFile).getId();
1678 boolean wasIndexed = false;
1679 List<ID<?, ?>> candidates = getAffectedIndexCandidates(virtualFile);
1680 for (ID<?, ?> candidate : candidates) {
1681 if (myRegisteredIndexes.isContentDependentIndex(candidate)) {
1682 if(getInputFilter(candidate).acceptInput(virtualFile)) {
1683 getIndex(candidate).resetIndexedStateForFile(fileId);
1689 getChangedFilesCollector().scheduleForUpdate(virtualFile);
1690 IndexingStamp.flushCache(fileId);
1696 public void removeIndexableSet(@NotNull IndexableFileSet set) {
1697 if (!myIndexableSetToProjectMap.containsKey(set)) return;
1698 myIndexableSets.remove(set);
1699 myIndexableSetToProjectMap.remove(set);
1701 ChangedFilesCollector changedFilesCollector = getChangedFilesCollector();
1702 for (VirtualFile file : changedFilesCollector.getAllFilesToUpdate()) {
1703 final int fileId = Math.abs(getIdMaskingNonIdBasedFile(file));
1704 if (!file.isValid()) {
1705 removeDataFromIndicesForFile(fileId, file);
1706 changedFilesCollector.removeFileIdFromFilesScheduledForUpdate(fileId);
1708 else if (getIndexableSetForFile(file) == null) { // todo remove data from indices for removed
1709 changedFilesCollector.removeFileIdFromFilesScheduledForUpdate(fileId);
1713 IndexingStamp.flushCaches();
1717 public VirtualFile findFileById(Project project, int id) {
1718 return IndexInfrastructure.findFileById((PersistentFS)ManagingFS.getInstance(), id);
1722 private static PsiFile findLatestKnownPsiForUncomittedDocument(@NotNull Document doc, @NotNull Project project) {
1723 return PsiDocumentManager.getInstance(project).getCachedPsiFile(doc);
1727 public static void cleanupProcessedFlag() {
1728 final VirtualFile[] roots = ManagingFS.getInstance().getRoots();
1729 for (VirtualFile root : roots) {
1730 cleanProcessedFlag(root);
1734 static void cleanProcessedFlag(@NotNull final VirtualFile file) {
1735 if (!(file instanceof VirtualFileSystemEntry)) return;
1737 final VirtualFileSystemEntry nvf = (VirtualFileSystemEntry)file;
1738 nvf.setFileIndexed(false);
1739 if (file.isDirectory()) {
1740 for (VirtualFile child : nvf.getCachedChildren()) {
1741 cleanProcessedFlag(child);
1746 void setUpFlusher() {
1747 myFlushingFuture = FlushingDaemon.everyFiveSeconds(new Runnable() {
1748 private final SerializationManagerEx mySerializationManager = SerializationManagerEx.getInstanceEx();
1749 private int lastModCount;
1753 mySerializationManager.flushNameStorage();
1755 int currentModCount = myLocalModCount.get();
1756 if (lastModCount == currentModCount) {
1757 flushAllIndices(lastModCount);
1759 lastModCount = currentModCount;
1765 public void invalidateCaches() {
1766 CorruptionMarker.requestInvalidation();
1772 public IntPredicate getAccessibleFileIdFilter(@Nullable Project project) {
1773 boolean dumb = ActionUtil.isDumbMode(project);
1774 if (!dumb) return f -> true;
1776 DumbModeAccessType dumbModeAccessType = getCurrentDumbModeAccessType();
1777 if (dumbModeAccessType == null) {
1778 //throw new IllegalStateException("index access is not allowed in dumb mode");
1782 if (dumbModeAccessType == DumbModeAccessType.RAW_INDEX_DATA_ACCEPTABLE) return f -> true;
1784 assert dumbModeAccessType == DumbModeAccessType.RELIABLE_DATA_ONLY;
1785 return fileId -> !getChangedFilesCollector().containsFileId(fileId);
1789 public void flushIndexes() {
1790 for (ID<?, ?> id : getRegisteredIndexes().getState().getIndexIDs()) {
1792 getIndex(id).flush();
1794 catch (StorageException e) {
1795 throw new RuntimeException(e);
1800 public static final boolean DO_TRACE_STUB_INDEX_UPDATE = SystemProperties.getBooleanProperty("idea.trace.stub.index.update", false);
1803 static <K, V> int getIndexExtensionVersion(@NotNull FileBasedIndexExtension<K, V> extension) {
1804 int version = extension.getVersion();
1806 if (VfsAwareMapReduceIndex.hasSnapshotMapping(extension)) {
1807 version += SnapshotInputMappings.getVersion();