fix diagnostic for cleared indexes
[idea/community.git] / platform / lang-impl / src / com / intellij / util / indexing / FileBasedIndexImpl.java
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;
3
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;
71
72 import java.io.File;
73 import java.io.IOException;
74 import java.lang.ref.SoftReference;
75 import java.lang.ref.WeakReference;
76 import java.util.*;
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;
87
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);
92
93   private volatile RegisteredIndexes myRegisteredIndexes;
94
95   private final PerIndexDocumentVersionMap myLastIndexedDocStamps = new PerIndexDocumentVersionMap();
96
97   // findExtensionOrFail is thread safe
98   private final NotNullLazyValue<ChangedFilesCollector> myChangedFilesCollector = NotNullLazyValue.createValue(()
99            -> AsyncEventSupport.EP_NAME.findExtensionOrFail(ChangedFilesCollector.class));
100
101   private final List<IndexableFileSet> myIndexableSets = ContainerUtil.createLockFreeCopyOnWriteList();
102   private final Map<IndexableFileSet, Project> myIndexableSetToProjectMap = new HashMap<>();
103
104   private final SimpleMessageBusConnection myConnection;
105   private final FileDocumentManager myFileDocumentManager;
106
107   private final Set<ID<?, ?>> myUpToDateIndicesForUnsavedOrTransactedDocuments = ContainerUtil.newConcurrentSet();
108   private volatile SmartFMap<Document, PsiFile> myTransactionMap = SmartFMap.emptyMap();
109
110   private final boolean myIsUnitTestMode;
111
112   @Nullable
113   private Runnable myShutDownTask;
114   @Nullable
115   private ScheduledFuture<?> myFlushingFuture;
116
117   private final AtomicInteger myLocalModCount = new AtomicInteger();
118   private final AtomicInteger myFilesModCount = new AtomicInteger();
119   private final Set<Project> myProjectsBeingUpdated = ContainerUtil.newConcurrentSet();
120
121   private final Lock myReadLock;
122   final Lock myWriteLock;
123
124   private IndexConfiguration getState() {
125     return myRegisteredIndexes.getConfigurationState();
126   }
127
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());
135
136     myRegisteredIndexes = null;
137   }
138
139   public FileBasedIndexImpl() {
140     ReadWriteLock lock = new ReentrantReadWriteLock();
141     myReadLock = lock.readLock();
142     myWriteLock = lock.writeLock();
143
144     myFileDocumentManager = FileDocumentManager.getInstance();
145     myIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
146
147     SimpleMessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().simpleConnect();
148     connection.subscribe(DynamicPluginListener.TOPIC, new FileBasedIndexPluginListener(this));
149
150     connection.subscribe(PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() {
151       @Override
152       public void transactionStarted(@NotNull final Document doc, @NotNull final PsiFile file) {
153         myTransactionMap = myTransactionMap.plus(doc, file);
154         clearUpToDateIndexesForUnsavedOrTransactedDocs();
155       }
156
157       @Override
158       public void transactionCompleted(@NotNull final Document doc, @NotNull final PsiFile file) {
159         myTransactionMap = myTransactionMap.minus(doc);
160       }
161     });
162
163     connection.subscribe(FileTypeManager.TOPIC, new FileBasedIndexFileTypeListener());
164
165     connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerListener() {
166       @Override
167       public void fileContentReloaded(@NotNull VirtualFile file, @NotNull Document document) {
168         cleanupMemoryStorage(true);
169       }
170
171       @Override
172       public void unsavedDocumentsDropped() {
173         cleanupMemoryStorage(false);
174       }
175     });
176
177     connection.subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener() {
178       @Override
179       public void appWillBeClosed(boolean isRestart) {
180         if (!myRegisteredIndexes.areIndexesReady()) {
181           new Task.Modal(null, IndexingBundle.message("indexes.preparing.to.shutdown.message"), false) {
182             @Override
183             public void run(@NotNull ProgressIndicator indicator) {
184               myRegisteredIndexes.waitUntilAllIndicesAreInitialized();
185             }
186           }.queue();
187         }
188       }
189     });
190
191     myConnection = connection;
192
193     FileBasedIndexExtension.EXTENSION_POINT_NAME.addExtensionPointListener(new ExtensionPointListener<FileBasedIndexExtension<?, ?>>() {
194       @Override
195       public void extensionRemoved(@NotNull FileBasedIndexExtension<?, ?> extension, @NotNull PluginDescriptor pluginDescriptor) {
196         ID.unloadId(extension.getName());
197       }
198     }, null);
199
200     initComponent();
201   }
202
203   void scheduleFullIndexesRescan(@NotNull Collection<ID<?, ?>> indexesToRebuild, @NotNull String reason) {
204     cleanupProcessedFlag();
205     doClearIndices(id -> indexesToRebuild.contains(id));
206     scheduleIndexRebuild(reason);
207   }
208
209   @VisibleForTesting
210   void doClearIndices(@NotNull Predicate<? super ID<?, ?>> filter) {
211     try {
212       waitUntilIndicesAreInitialized();
213     }
214     catch (ProcessCanceledException e) {
215       // will be rebuilt on re-scan
216       return;
217     }
218     IndexingStamp.flushCaches();
219
220     List<ID<?, ?>> clearedIndexes = new ArrayList<>();
221     List<ID<?, ?>> survivedIndexes = new ArrayList<>();
222     for (ID<?, ?> indexId : getState().getIndexIDs()) {
223       if (filter.test(indexId)) {
224         try {
225           clearIndex(indexId);
226         } catch (StorageException e) {
227           LOG.info(e);
228         } catch (Exception e) {
229           LOG.error(e);
230         }
231         clearedIndexes.add(indexId);
232       } else {
233         survivedIndexes.add(indexId);
234       }
235     }
236
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(", ")));
239   }
240
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))
246       .distinct()
247       .mapToInt(f -> processor.process(f) ? 1 : 0)
248       .allMatch(success -> success == 1);
249   }
250
251   RegisteredIndexes getRegisteredIndexes() {
252     return myRegisteredIndexes;
253   }
254
255   void setUpShutDownTask() {
256     myShutDownTask = new MyShutDownTask();
257     ShutDownTracker.getInstance().registerShutdownTask(myShutDownTask);
258   }
259
260   @ApiStatus.Internal
261   public void dumpIndexStatistics() {
262     IndexConfiguration state = getRegisteredIndexes().getState();
263     for (ID<?, ?> id : state.getIndexIDs()) {
264       state.getIndex(id).dumpStatistics();
265     }
266   }
267
268   static class MyShutDownTask implements Runnable {
269     @Override
270     public void run() {
271       FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance();
272       if (fileBasedIndex instanceof FileBasedIndexImpl) {
273         ((FileBasedIndexImpl)fileBasedIndex).performShutdown(false);
274       }
275     }
276   }
277
278   public static boolean isProjectOrWorkspaceFile(@NotNull VirtualFile file, @Nullable FileType fileType) {
279     return ProjectCoreUtil.isProjectOrWorkspaceFile(file, fileType);
280   }
281
282   static boolean belongsToScope(VirtualFile file, VirtualFile restrictedTo, GlobalSearchScope filter) {
283     if (!(file instanceof VirtualFileWithId) || !file.isValid()) {
284       return false;
285     }
286
287     return (restrictedTo == null || Comparing.equal(file, restrictedTo)) &&
288            (filter == null || restrictedTo != null || filter.accept(file));
289   }
290
291   @Override
292   public void requestReindex(@NotNull VirtualFile file) {
293     requestReindex(file, true);
294   }
295
296   @ApiStatus.Internal
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();
307     }
308   }
309
310   void initComponent() {
311     LOG.assertTrue(myRegisteredIndexes == null);
312     myStorageBufferingHandler.resetState();
313     myRegisteredIndexes = new RegisteredIndexes(myFileDocumentManager, this);
314   }
315
316   @Override
317   public void waitUntilIndicesAreInitialized() {
318     if (myRegisteredIndexes == null) {
319       // interrupt all calculation while plugin reload
320       throw new ProcessCanceledException();
321     }
322     myRegisteredIndexes.waitUntilIndicesAreInitialized();
323   }
324
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);
329
330     final File versionFile = IndexInfrastructure.getVersionFile(name);
331
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();
336
337       if (extension.hasSnapshotMapping() && versionFileExisted) {
338         FileUtil.deleteWithRenaming(IndexInfrastructure.getPersistentIndexRootDir(name));
339       }
340       File rootDir = IndexInfrastructure.getIndexRootDir(name);
341       if (versionFileExisted) FileUtil.deleteWithRenaming(rootDir);
342       IndexingStamp.rewriteVersion(name, version);
343
344       try {
345         if (versionFileExisted) {
346           for (FileBasedIndexInfrastructureExtension ex : FileBasedIndexInfrastructureExtension.EP_NAME.getExtensionList()) {
347             ex.onFileBasedIndexVersionChanged(name);
348           }
349         }
350       } catch (Exception e) {
351         LOG.error(e);
352       }
353     }
354
355     initIndexStorage(extension, version, state, registrationStatusSink);
356   }
357
358   private static <K, V> void initIndexStorage(@NotNull FileBasedIndexExtension<K, V> extension,
359                                               int version,
360                                               @NotNull IndexConfiguration state,
361                                               @NotNull IndexVersionRegistrationSink registrationStatusSink)
362     throws IOException {
363     VfsAwareIndexStorage<K, V> storage = null;
364     final ID<K, V> name = extension.getName();
365     boolean contentHashesEnumeratorOk = false;
366
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);
373       });
374     }
375     else {
376       addedTypes = null;
377     }
378
379     for (int attempt = 0; attempt < 2; attempt++) {
380       try {
381         if (VfsAwareMapReduceIndex.hasSnapshotMapping(extension)) {
382           contentHashesEnumeratorOk = SnapshotHashEnumeratorService.getInstance().initialize();
383           if (!contentHashesEnumeratorOk) {
384             throw new IOException("content hash enumerator will be forcibly clean");
385           }
386         }
387
388         storage = createIndexStorage(extension);
389
390         UpdatableIndex<K, V, FileContent> index = createIndex(extension, new TransientChangesIndexStorage<>(storage, name));
391
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;
396           }
397         }
398
399         state.registerIndex(name,
400                             index,
401                             file -> file instanceof VirtualFileWithId && inputFilter.acceptInput(file) &&
402                                   !GlobalIndexFilter.isExcludedFromIndexViaFilters(file, name),
403                             version + GlobalIndexFilter.getFiltersVersion(name),
404                             addedTypes);
405         break;
406       }
407       catch (Exception e) {
408         if (ApplicationManager.getApplication().isUnitTestMode()) {
409           LOG.error(e);
410         }
411         else {
412           LOG.info(e);
413         }
414         boolean instantiatedStorage = storage != null;
415         try {
416           if (storage != null) storage.close();
417           storage = null;
418         }
419         catch (Exception ignored) {
420         }
421
422         FileUtil.deleteWithRenaming(IndexInfrastructure.getIndexRootDir(name));
423
424         if (extension.hasSnapshotMapping() && (!contentHashesEnumeratorOk || instantiatedStorage)) {
425           FileUtil.deleteWithRenaming(IndexInfrastructure.getPersistentIndexRootDir(name)); // todo there is possibility of corruption of storage and content hashes
426         }
427         registrationStatusSink.setIndexVersionDiff(name, new IndexingStamp.IndexVersionDiff.CorruptedRebuild(version));
428         IndexingStamp.rewriteVersion(name, version);
429       }
430     }
431   }
432
433   @NotNull
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<>();
437     }
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()
446     );
447   }
448
449   @NotNull
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);
456   }
457
458   void performShutdown(boolean keepConnection) {
459     RegisteredIndexes registeredIndexes = myRegisteredIndexes;
460     if (registeredIndexes == null || !registeredIndexes.performShutdown()) {
461       return; // already shut down
462     }
463
464     registeredIndexes.waitUntilAllIndicesAreInitialized();
465     try {
466       if (myShutDownTask != null) {
467         ShutDownTracker.getInstance().unregisterShutdownTask(myShutDownTask);
468       }
469       if (myFlushingFuture != null) {
470         myFlushingFuture.cancel(false);
471         myFlushingFuture = null;
472       }
473     }
474     finally {
475       LOG.info("START INDEX SHUTDOWN");
476       try {
477         PersistentIndicesConfiguration.saveConfiguration();
478
479         for (VirtualFile file : getChangedFilesCollector().getAllPossibleFilesToUpdate()) {
480           int fileId = getIdMaskingNonIdBasedFile(file);
481           if (file.isValid()) {
482             dropNontrivialIndexedStates(fileId);
483           }
484           else {
485             removeDataFromIndicesForFile(Math.abs(fileId), file);
486           }
487         }
488         getChangedFilesCollector().clearFilesToUpdate();
489
490         IndexingStamp.flushCaches();
491
492         IndexConfiguration state = getState();
493         for (ID<?, ?> indexId : state.getIndexIDs()) {
494           try {
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
499             }
500             index.dispose();
501           } catch (Throwable throwable) {
502             LOG.info("Problem disposing " + indexId, throwable);
503           }
504         }
505
506         FileBasedIndexInfrastructureExtension.EP_NAME.extensions().forEach(ex -> ex.shutdown());
507         SnapshotHashEnumeratorService.getInstance().close();
508         if (!keepConnection) {
509           myConnection.disconnect();
510         }
511       }
512       catch (Throwable e) {
513         LOG.error("Problems during index shutdown", e);
514       }
515       LOG.info("END INDEX SHUTDOWN");
516     }
517   }
518
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);
522
523     if (!states.isEmpty()) {
524       ProgressManager.getInstance().executeNonCancelableSection(() -> removeFileDataFromIndices(states, fileId, originalFile));
525     }
526   }
527
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);
531
532     Throwable unexpectedError = null;
533     for (ID<?, ?> indexId : affectedIndices) {
534       try {
535         updateSingleIndex(indexId, null, inputId, null);
536       }
537       catch (ProcessCanceledException pce) {
538         LOG.error(pce);
539       }
540       catch (Throwable e) {
541         LOG.info(e);
542         if (unexpectedError == null) {
543           unexpectedError = e;
544         }
545       }
546     }
547     IndexingStamp.flushCache(inputId);
548
549     if (unexpectedError != null) {
550       LOG.error(unexpectedError);
551     }
552   }
553
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);
557       if (index == null) {
558         throw new AssertionError("index '" + indexId.getName() + "' can't be found among registered indexes: " + myRegisteredIndexes.getState().getIndexIDs());
559       }
560       index.removeTransientDataForFile(inputId);
561     }
562
563     Document document = myFileDocumentManager.getCachedDocument(file);
564     if (document != null) {
565       myLastIndexedDocStamps.clearForDocument(document);
566       document.putUserData(ourFileContentKey, null);
567     }
568
569     clearUpToDateIndexesForUnsavedOrTransactedDocs();
570   }
571
572   private void flushAllIndices(final long modCount) {
573     if (HeavyProcessLatch.INSTANCE.isRunning()) {
574       return;
575     }
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
581       }
582       try {
583         final UpdatableIndex<?, ?, FileContent> index = state.getIndex(indexId);
584         if (index != null) {
585           index.flush();
586         }
587       }
588       catch (Throwable e) {
589         requestRebuild(indexId, e);
590       }
591     }
592
593     SnapshotHashEnumeratorService.getInstance().flush();
594   }
595
596   private static final ThreadLocal<Integer> myUpToDateCheckState = new ThreadLocal<>();
597
598   public static <T,E extends Throwable> T disableUpToDateCheckIn(@NotNull ThrowableComputable<T, E> runnable) throws E {
599     disableUpToDateCheckForCurrentThread();
600     try {
601       return runnable.compute();
602     }
603     finally {
604       enableUpToDateCheckForCurrentThread();
605     }
606   }
607   private static void disableUpToDateCheckForCurrentThread() {
608     final Integer currentValue = myUpToDateCheckState.get();
609     myUpToDateCheckState.set(currentValue == null ? 1 : currentValue.intValue() + 1);
610   }
611
612   private static void enableUpToDateCheckForCurrentThread() {
613     final Integer currentValue = myUpToDateCheckState.get();
614     if (currentValue != null) {
615       final int newValue = currentValue.intValue() - 1;
616       if (newValue != 0) {
617         myUpToDateCheckState.set(newValue);
618       }
619       else {
620         myUpToDateCheckState.remove();
621       }
622     }
623   }
624
625   static boolean isUpToDateCheckEnabled() {
626     final Integer value = myUpToDateCheckState.get();
627     return value == null || value.intValue() == 0;
628   }
629
630   private final ThreadLocal<Boolean> myReentrancyGuard = ThreadLocal.withInitial(() -> Boolean.FALSE);
631
632   @Override
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();
640
641     NoAccessDuringPsiEvents.checkCallContext(indexId);
642
643     if (!needsFileContentLoading(indexId)) {
644       return true; //indexed eagerly in foreground while building unindexed file list
645     }
646     if (filter == GlobalSearchScope.EMPTY_SCOPE) {
647       return false;
648     }
649     if (ActionUtil.isDumbMode(project) && getCurrentDumbModeAccessType_NoDumbChecks() == null) {
650       handleDumbMode(project);
651     }
652
653     if (myReentrancyGuard.get().booleanValue()) {
654       //assert false : "ensureUpToDate() is not reentrant!";
655       return true;
656     }
657     myReentrancyGuard.set(Boolean.TRUE);
658
659     try {
660       if (isUpToDateCheckEnabled()) {
661         try {
662           if (!RebuildStatus.isOk(indexId)) {
663             if (getCurrentDumbModeAccessType_NoDumbChecks() == null) {
664               throw new ServiceNotReadyException();
665             }
666             return false;
667           }
668           if (!ActionUtil.isDumbMode(project) || getCurrentDumbModeAccessType_NoDumbChecks() == null) {
669             forceUpdate(project, filter, restrictedFile);
670           }
671           if (!areUnsavedDocumentsIndexed(indexId)) { // todo: check scope ?
672             indexUnsavedDocuments(indexId, project, filter, restrictedFile);
673           }
674         }
675         catch (RuntimeException e) {
676           final Throwable cause = e.getCause();
677           if (cause instanceof StorageException || cause instanceof IOException) {
678             scheduleRebuild(indexId, e);
679           }
680           else {
681             throw e;
682           }
683         }
684       }
685     }
686     finally {
687       myReentrancyGuard.set(Boolean.FALSE);
688     }
689     return true;
690   }
691
692   private boolean areUnsavedDocumentsIndexed(@NotNull ID<?, ?> indexId) {
693     return myUpToDateIndicesForUnsavedOrTransactedDocuments.contains(indexId);
694   }
695
696   private static void handleDumbMode(@Nullable Project project) throws IndexNotReadyException {
697     ProgressManager.checkCanceled();
698     throw IndexNotReadyException.create(project == null ? null : DumbServiceImpl.getInstance(project).getDumbModeStartTrace());
699   }
700
701   private static final Key<SoftReference<ProjectIndexableFilesFilter>> ourProjectFilesSetKey = Key.create("projectFiles");
702
703   @TestOnly
704   public void cleanupForNextTest() {
705     getChangedFilesCollector().ensureUpToDate();
706
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();
713     }
714   }
715
716   @ApiStatus.Internal
717   public ChangedFilesCollector getChangedFilesCollector() {
718     return myChangedFilesCollector.getValue();
719   }
720
721   void incrementFilesModCount() {
722     myFilesModCount.incrementAndGet();
723   }
724
725   void filesUpdateStarted(Project project) {
726     getChangedFilesCollector().ensureUpToDate();
727     myProjectsBeingUpdated.add(project);
728     incrementFilesModCount();
729   }
730
731   void filesUpdateFinished(@NotNull Project project) {
732     myProjectsBeingUpdated.remove(project);
733     incrementFilesModCount();
734   }
735
736   private final Lock myCalcIndexableFilesLock = new ReentrantLock();
737
738   @Override
739   @Nullable
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;
743
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;
748
749     if (myCalcIndexableFilesLock.tryLock()) { // make best effort for calculating filter
750       try {
751         reference = project.getUserData(ourProjectFilesSetKey);
752         data = com.intellij.reference.SoftReference.dereference(reference);
753         if (data != null) {
754           if (data.getModificationCount() == currentFileModCount) {
755             return data;
756           }
757         } else if (!isUpToDateCheckEnabled()) {
758           return null;
759         }
760
761         long start = System.currentTimeMillis();
762
763         IntArrayList fileSet = new IntArrayList();
764         iterateIndexableFiles(fileOrDir -> {
765           if (fileOrDir instanceof VirtualFileWithId) {
766             fileSet.add(((VirtualFileWithId)fileOrDir).getId());
767           }
768           return true;
769         }, project, null);
770         ProjectIndexableFilesFilter filter = new ProjectIndexableFilesFilter(fileSet, currentFileModCount);
771         project.putUserData(ourProjectFilesSetKey, new SoftReference<>(filter));
772
773         long finish = System.currentTimeMillis();
774         LOG.debug(fileSet.size() + " files iterated in " + (finish - start) + " ms");
775
776         return filter;
777       }
778       finally {
779         myCalcIndexableFilesLock.unlock();
780       }
781     }
782     return null; // ok, no filtering
783   }
784
785   @Nullable
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
789       return null;
790     }
791     if (e instanceof ProcessCanceledException) {
792       return null;
793     }
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.
797       return null;
798     }
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
804     ) {
805       return cause;
806     }
807     return null;
808   }
809
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));
814     }
815   }
816
817   void clearIndicesIfNecessary() {
818     waitUntilIndicesAreInitialized();
819     for (ID<?, ?> indexId : getState().getIndexIDs()) {
820       try {
821         RebuildStatus.clearIndexIfNecessary(indexId, getIndex(indexId)::clear);
822       }
823       catch (StorageException e) {
824         requestRebuild(indexId);
825         LOG.error(e);
826       }
827     }
828   }
829
830   void clearIndex(@NotNull final ID<?, ?> indexId) throws StorageException {
831     advanceIndexVersion(indexId);
832
833     final UpdatableIndex<?, ?, FileContent> index = myRegisteredIndexes.getState().getIndex(indexId);
834     assert index != null : "Index with key " + indexId + " not found or not registered properly";
835     index.clear();
836   }
837
838   private void advanceIndexVersion(ID<?, ?> indexId) {
839     try {
840       IndexingStamp.rewriteVersion(indexId, myRegisteredIndexes.getState().getIndexVersion(indexId));
841     }
842     catch (IOException e) {
843       LOG.error(e);
844     }
845   }
846
847   @NotNull
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);
853   }
854
855   @NotNull
856   private Set<Document> getTransactedDocuments() {
857     return myTransactionMap.keySet();
858   }
859
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 ?
866     }
867
868     Collection<Document> documents = getUnsavedDocuments();
869     Set<Document> transactedDocuments = getTransactedDocuments();
870     if (documents.isEmpty()) {
871       documents = transactedDocuments;
872     }
873     else if (!transactedDocuments.isEmpty()) {
874       documents = new HashSet<>(documents);
875       documents.addAll(transactedDocuments);
876     }
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;
881       else {
882         if (!(documents instanceof HashSet)) documents = new HashSet<>(documents);
883
884         documents.addAll(uncommittedDocumentsCollection);
885       }
886     }
887
888     if (!documents.isEmpty()) {
889       Collection<Document> documentsToProcessForProject = ContainerUtil.filter(documents,
890                                                                                document -> belongsToScope(myFileDocumentManager.getFile(document), restrictedFile, filter));
891
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;
895
896         if(myStorageBufferingHandler.runUpdate(true, () -> task.processAll(documentsToProcessForProject, project)) &&
897            documentsToProcessForProject.size() == documents.size() &&
898            !hasActiveTransactions()
899           ) {
900           ProgressManager.checkCanceled();
901           myUpToDateIndicesForUnsavedOrTransactedDocuments.add(indexId);
902         }
903       }
904     }
905   }
906
907   private boolean hasActiveTransactions() {
908     return !myTransactionMap.isEmpty();
909   }
910
911
912   private static final Key<WeakReference<FileContentImpl>> ourFileContentKey = Key.create("unsaved.document.index.content");
913
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);
920
921     final DocumentContent content;
922     if (dominantContentFile != null && dominantContentFile.getViewProvider().getModificationStamp() != document.getModificationStamp()) {
923       content = new PsiContent(document, dominantContentFile);
924     }
925     else {
926       content = new AuthenticContent(document);
927     }
928
929     final long currentDocStamp = PsiDocumentManager.getInstance(project).getLastCommittedStamp(document);
930
931     final long previousDocStamp = myLastIndexedDocStamps.get(document, requestedIndexId);
932     if (previousDocStamp == currentDocStamp) return;
933
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));
939
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;
947           }
948           else {
949             newFc = new FileContentImpl(vFile, contentText, currentDocStamp);
950             document.putUserData(ourFileContentKey, new WeakReference<>(newFc));
951           }
952
953           initFileContent(newFc, project, dominantContentFile);
954           newFc.ensureThreadSafeLighterAST();
955
956           if (content instanceof AuthenticContent) {
957             newFc.putUserData(PlatformIdTableBuilding.EDITOR_HIGHLIGHTER,
958                               EditorHighlighterCache.getEditorHighlighterForCachesBuilding(document));
959           }
960
961           markFileIndexed(vFile);
962           try {
963             getIndex(requestedIndexId).mapInputAndPrepareUpdate(inputId, newFc).compute();
964           }
965           finally {
966             unmarkBeingIndexed();
967             cleanFileContent(newFc, dominantContentFile);
968           }
969         }
970         else { // effectively wipe the data from the indices
971           getIndex(requestedIndexId).mapInputAndPrepareUpdate(inputId, null).compute();
972         }
973       }
974
975       long previousState = myLastIndexedDocStamps.set(document, requestedIndexId, currentDocStamp);
976       assert previousState == previousDocStamp;
977     });
978   }
979
980   @NotNull
981   @Override
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);
985     }
986
987     return super.getFileData(id, virtualFile, project);
988   }
989
990   @SuppressWarnings({"unchecked", "rawtypes"})
991   @NotNull
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, () -> {
996         try {
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);
1002         }
1003         catch (IOException e) {
1004           throw new RuntimeException(e);
1005         }
1006       });
1007       return indexValues.get(id);
1008     }
1009     return Collections.emptyMap();
1010   }
1011
1012
1013   private final StorageBufferingHandler myStorageBufferingHandler = new StorageBufferingHandler() {
1014     @NotNull
1015     @Override
1016     protected Stream<UpdatableIndex<?, ?, ?>> getIndexes() {
1017       IndexConfiguration state = getState();
1018       return state.getIndexIDs().stream().map(id -> state.getIndex(id));
1019     }
1020   };
1021
1022   @ApiStatus.Internal
1023   public void runCleanupAction(@NotNull Runnable cleanupAction) {
1024     Computable<Boolean> updateComputable = () -> {
1025       cleanupAction.run();
1026       return true;
1027     };
1028     myStorageBufferingHandler.runUpdate(false, updateComputable);
1029     myStorageBufferingHandler.runUpdate(true, updateComputable);
1030   }
1031
1032   void cleanupMemoryStorage(boolean skipContentDependentIndexes) {
1033     myLastIndexedDocStamps.clear();
1034     if (myRegisteredIndexes == null) {
1035       // unsaved doc is dropped while plugin load/unload-ing
1036       return;
1037     }
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)
1042       return;
1043     }
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();
1049     }
1050   }
1051
1052   @Override
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);
1058         return null;
1059       });
1060     }
1061     else {
1062       doRequestRebuild(indexId, throwable);
1063     }
1064   }
1065
1066   private void doRequestRebuild(@NotNull ID<?, ?> indexId, Throwable throwable) {
1067     cleanupProcessedFlag();
1068     if (!myRegisteredIndexes.isExtensionsDataLoaded()) reportUnexpectedAsyncInitState();
1069
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);
1077       }
1078       else {
1079         LOG.info(message, throwable);
1080       }
1081
1082       cleanupProcessedFlag();
1083
1084       if (!myRegisteredIndexes.isInitialized()) return;
1085       advanceIndexVersion(indexId);
1086
1087       Runnable rebuildRunnable = () -> scheduleIndexRebuild("checkRebuild");
1088
1089       if (myIsUnitTestMode) {
1090         rebuildRunnable.run();
1091       }
1092       else {
1093         // we do invoke later since we can have read lock acquired
1094         AppUIExecutor.onWriteThread().later().expireWith(app).submit(rebuildRunnable);
1095       }
1096     }
1097   }
1098
1099   private static void reportUnexpectedAsyncInitState() {
1100     LOG.error("Unexpected async indices initialization problem");
1101   }
1102
1103   @Override
1104   public <K, V> UpdatableIndex<K, V, FileContent> getIndex(ID<K, V> indexId) {
1105     return getState().getIndex(indexId);
1106   }
1107
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();
1113     }
1114
1115     return getState().getInputFilter(indexId);
1116   }
1117
1118   @NotNull
1119   Collection<VirtualFile> getFilesToUpdate(final Project project) {
1120     return ContainerUtil.filter(getChangedFilesCollector().getAllFilesToUpdate(), filesToBeIndexedForProjectCondition(project)::test);
1121   }
1122
1123   @NotNull
1124   private Predicate<VirtualFile> filesToBeIndexedForProjectCondition(Project project) {
1125     return virtualFile -> {
1126         if (!virtualFile.isValid()) {
1127           return true;
1128         }
1129
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
1134           }
1135           if (ReadAction.compute(() -> set.isInSet(virtualFile))) {
1136             return true;
1137           }
1138         }
1139         return false;
1140       };
1141   }
1142
1143   public boolean isFileUpToDate(VirtualFile file) {
1144     return !getChangedFilesCollector().isScheduledForUpdate(file);
1145   }
1146
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);
1153     }
1154   }
1155
1156   @ApiStatus.Internal
1157   @NotNull
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));
1162
1163     FileIndexingResult indexingResult;
1164     try {
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);
1169       }
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);
1176         } else {
1177           indexingResult = new FileIndexingResult(true, Collections.emptyMap(), Collections.emptyMap(), file.getFileType());
1178         }
1179       }
1180       else {
1181         indexingResult = doIndexFileContent(project, content);
1182       }
1183
1184       if (indexingResult.setIndexedStatus && file instanceof VirtualFileSystemEntry) {
1185         ((VirtualFileSystemEntry)file).setFileIndexed(true);
1186       }
1187       if (VfsEventsMerger.LOG != null) {
1188         VfsEventsMerger.LOG.info("File " + file +
1189                                  " indexes have been updated for indexes " + indexingResult.updateTimesPerIndexer.keySet() +
1190                                  " and deleted for " + indexingResult.deletionTimesPerIndexer.keySet());
1191       }
1192       getChangedFilesCollector().removeFileIdFromFilesScheduledForUpdate(fileId);
1193       // Indexing time takes only input data mapping time into account.
1194       long indexingTime =
1195         indexingResult.updateTimesPerIndexer.values().stream().mapToLong(e -> e).sum() +
1196         indexingResult.deletionTimesPerIndexer.values().stream().mapToLong(e -> e).sum();
1197       return new FileIndexingStatistics(indexingTime,
1198                                         indexingResult.fileType,
1199                                         ContainerUtil.union(indexingResult.updateTimesPerIndexer, indexingResult.deletionTimesPerIndexer));
1200     }
1201     finally {
1202       IndexingStamp.flushCache(fileId);
1203     }
1204   }
1205
1206   private static final class FileIndexingResult {
1207     public final boolean setIndexedStatus;
1208     public final Map<ID<?, ?>, Long> updateTimesPerIndexer;
1209     public final Map<ID<?, ?>, Long> deletionTimesPerIndexer;
1210     public final FileType fileType;
1211
1212     private FileIndexingResult(boolean setIndexedStatus,
1213                                @NotNull Map<ID<?, ?>, Long> updateTimesPerIndexer,
1214                                @NotNull Map<ID<?, ?>, Long> deletionTimesPerIndexer,
1215                                @NotNull FileType type) {
1216       this.setIndexedStatus = setIndexedStatus;
1217       this.updateTimesPerIndexer = updateTimesPerIndexer;
1218       this.deletionTimesPerIndexer = deletionTimesPerIndexer;
1219       fileType = type;
1220     }
1221   }
1222
1223   private static final class SingleIndexUpdateStats {
1224     public final long mapInputTime;
1225
1226     private SingleIndexUpdateStats(long mapInputTime) {
1227       this.mapInputTime = mapInputTime;
1228     }
1229   }
1230
1231   @NotNull
1232   private FileBasedIndexImpl.FileIndexingResult doIndexFileContent(@Nullable Project project, @NotNull CachedFileContent content) {
1233     ProgressManager.checkCanceled();
1234     final VirtualFile file = content.getVirtualFile();
1235     Ref<Boolean> setIndexedStatus = Ref.create(Boolean.TRUE);
1236     Map<ID<?, ?>, Long> perIndexerUpdateTimes = new HashMap<>();
1237     Map<ID<?, ?>, Long> perIndexerDeletionTimes = new HashMap<>();
1238     Ref<FileType> fileTypeRef = Ref.create();
1239
1240     getFileTypeManager().freezeFileTypeTemporarilyIn(file, () -> {
1241       ProgressManager.checkCanceled();
1242
1243       FileContentImpl fc = null;
1244       PsiFile psiFile = null;
1245
1246       int inputId = Math.abs(getFileId(file));
1247       Set<ID<?, ?>> currentIndexedStates = new HashSet<>(IndexingStamp.getNontrivialFileIndexedStates(inputId));
1248       List<ID<?, ?>> affectedIndexCandidates = getAffectedIndexCandidates(file);
1249       //noinspection ForLoopReplaceableByForEach
1250       for (int i = 0, size = affectedIndexCandidates.size(); i < size; ++i) {
1251         try {
1252           ProgressManager.checkCanceled();
1253
1254           if (fc == null) {
1255             fc = new LazyFileContentImpl(file, () -> getBytesOrNull(content));
1256
1257             ProgressManager.checkCanceled();
1258
1259             psiFile = content.getUserData(IndexingDataKeys.PSI_FILE);
1260             initFileContent(fc, project == null ? ProjectUtil.guessProjectForFile(file) : project, psiFile);
1261
1262             fileTypeRef.set(fc.getFileType());
1263
1264             ProgressManager.checkCanceled();
1265           }
1266
1267           final ID<?, ?> indexId = affectedIndexCandidates.get(i);
1268           if (getInputFilter(indexId).acceptInput(file) && getIndexingState(fc, indexId).updateRequired()) {
1269             ProgressManager.checkCanceled();
1270             SingleIndexUpdateStats updateStats = updateSingleIndex(indexId, file, inputId, fc);
1271             if (updateStats == null) {
1272               setIndexedStatus.set(Boolean.FALSE);
1273             } else {
1274               perIndexerUpdateTimes.put(indexId, updateStats.mapInputTime);
1275             }
1276             currentIndexedStates.remove(indexId);
1277           }
1278         }
1279         catch (ProcessCanceledException e) {
1280           cleanFileContent(fc, psiFile);
1281           throw e;
1282         }
1283       }
1284
1285       if (psiFile != null) {
1286         psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1287       }
1288
1289       boolean shouldClearAllIndexedStates = fc == null;
1290       for (ID<?, ?> indexId : currentIndexedStates) {
1291         ProgressManager.checkCanceled();
1292         if (shouldClearAllIndexedStates || getIndex(indexId).getIndexingStateForFile(inputId, fc).updateRequired()) {
1293           ProgressManager.checkCanceled();
1294           SingleIndexUpdateStats updateStats = updateSingleIndex(indexId, file, inputId, null);
1295           if (updateStats == null) {
1296             setIndexedStatus.set(Boolean.FALSE);
1297           } else {
1298             perIndexerDeletionTimes.put(indexId, updateStats.mapInputTime);
1299           }
1300         }
1301       }
1302
1303       fileTypeRef.set(fc != null ? fc.getFileType() : file.getFileType());
1304     });
1305
1306     file.putUserData(IndexingDataKeys.REBUILD_REQUESTED, null);
1307     return new FileIndexingResult(setIndexedStatus.get(), perIndexerUpdateTimes, perIndexerDeletionTimes, fileTypeRef.get());
1308   }
1309
1310   private static byte @NotNull[] getBytesOrNull(@NotNull CachedFileContent content) {
1311     try {
1312       return content.getBytes();
1313     } catch (IOException e) {
1314       return ArrayUtilRt.EMPTY_BYTE_ARRAY;
1315     }
1316   }
1317
1318   @Override
1319   public boolean isIndexingCandidate(@NotNull VirtualFile file, @NotNull ID<?, ?> indexId) {
1320     return !isTooLarge(file) && getAffectedIndexCandidates(file).contains(indexId);
1321   }
1322
1323   @NotNull
1324   List<ID<?, ?>> getAffectedIndexCandidates(@NotNull VirtualFile file) {
1325     if (file.isDirectory()) {
1326       return isProjectOrWorkspaceFile(file, null) ? Collections.emptyList() : myRegisteredIndexes.getIndicesForDirectories();
1327     }
1328     FileType fileType = file.getFileType();
1329     if(isProjectOrWorkspaceFile(file, fileType)) return Collections.emptyList();
1330
1331     return getState().getFileTypesForIndex(fileType);
1332   }
1333
1334   private static void cleanFileContent(FileContentImpl fc, PsiFile psiFile) {
1335     if (fc == null) return;
1336     if (psiFile != null) psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1337     fc.putUserData(IndexingDataKeys.PSI_FILE, null);
1338   }
1339
1340   private static void initFileContent(@NotNull FileContentImpl fc, Project project, PsiFile psiFile) {
1341     if (psiFile != null) {
1342       psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true);
1343       fc.putUserData(IndexingDataKeys.PSI_FILE, psiFile);
1344     }
1345
1346     fc.setProject(project);
1347   }
1348
1349   @Nullable("null in case index update is not necessary or the update has failed")
1350   SingleIndexUpdateStats updateSingleIndex(@NotNull ID<?, ?> indexId, @Nullable VirtualFile file, int inputId, @Nullable FileContent currentFC) {
1351     if (!myRegisteredIndexes.isExtensionsDataLoaded()) reportUnexpectedAsyncInitState();
1352     if (!RebuildStatus.isOk(indexId) && !myIsUnitTestMode) {
1353       return null; // the index is scheduled for rebuild, no need to update
1354     }
1355     myLocalModCount.incrementAndGet();
1356
1357     final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
1358     assert index != null;
1359
1360     markFileIndexed(file);
1361     try {
1362       Computable<Boolean> storageUpdate;
1363       long mapInputTime = System.nanoTime();
1364       try {
1365         // Propagate MapReduceIndex.MapInputException and ProcessCancelledException happening on input mapping.
1366         storageUpdate = index.mapInputAndPrepareUpdate(inputId, currentFC);
1367       } finally {
1368         mapInputTime = System.nanoTime() - mapInputTime;
1369       }
1370       if (myStorageBufferingHandler.runUpdate(false, storageUpdate)) {
1371         ConcurrencyUtil.withLock(myReadLock, () -> {
1372           if (currentFC != null) {
1373             if (!isMock(currentFC.getFile())) {
1374               index.setIndexedStateForFile(inputId, currentFC);
1375             }
1376           }
1377           else {
1378             index.resetIndexedStateForFile(inputId);
1379           }
1380         });
1381       }
1382       return new SingleIndexUpdateStats(mapInputTime);
1383     }
1384     catch (RuntimeException exception) {
1385       Throwable causeToRebuildIndex = getCauseToRebuildIndex(exception);
1386       if (causeToRebuildIndex != null) {
1387         requestRebuild(indexId, exception);
1388         return null;
1389       }
1390       throw exception;
1391     }
1392     finally {
1393       unmarkBeingIndexed();
1394     }
1395   }
1396
1397   private static void markFileIndexed(@Nullable VirtualFile file) {
1398     if (ourIndexedFile.get() != null || ourFileToBeIndexed.get() != null) {
1399       throw new AssertionError("Reentrant indexing");
1400     }
1401     ourIndexedFile.set(file);
1402   }
1403
1404   private static void unmarkBeingIndexed() {
1405     ourIndexedFile.remove();
1406   }
1407
1408   @Override
1409   public VirtualFile getFileBeingCurrentlyIndexed() {
1410     return ourIndexedFile.get();
1411   }
1412
1413   private class VirtualFileUpdateTask extends UpdateTask<VirtualFile> {
1414     @Override
1415     void doProcess(VirtualFile item, Project project) {
1416       processRefreshedFile(project, new CachedFileContent(item));
1417     }
1418   }
1419
1420   private final VirtualFileUpdateTask myForceUpdateTask = new VirtualFileUpdateTask();
1421   private volatile long myLastOtherProjectInclusionStamp;
1422
1423   private void forceUpdate(@Nullable Project project, @Nullable final GlobalSearchScope filter, @Nullable final VirtualFile restrictedTo) {
1424     Collection<VirtualFile> allFilesToUpdate = getChangedFilesCollector().getAllFilesToUpdate();
1425
1426     if (!allFilesToUpdate.isEmpty()) {
1427       boolean includeFilesFromOtherProjects = restrictedTo == null && System.currentTimeMillis() - myLastOtherProjectInclusionStamp > 100;
1428       List<VirtualFile> virtualFilesToBeUpdatedForProject = ContainerUtil.filter(
1429         allFilesToUpdate,
1430         new ProjectFilesCondition(projectIndexableFiles(project), filter, restrictedTo,
1431                                   includeFilesFromOtherProjects)
1432       );
1433
1434       if (!virtualFilesToBeUpdatedForProject.isEmpty()) {
1435         myForceUpdateTask.processAll(virtualFilesToBeUpdatedForProject, project);
1436       }
1437       if (includeFilesFromOtherProjects) {
1438         myLastOtherProjectInclusionStamp = System.currentTimeMillis();
1439       }
1440     }
1441   }
1442
1443   public boolean needsFileContentLoading(@NotNull ID<?, ?> indexId) {
1444     return myRegisteredIndexes.isContentDependentIndex(indexId);
1445   }
1446
1447   @Nullable IndexableFileSet getIndexableSetForFile(VirtualFile file) {
1448     for (IndexableFileSet set : myIndexableSets) {
1449       if (set.isInSet(file)) {
1450         return set;
1451       }
1452     }
1453     return null;
1454   }
1455
1456   @NotNull List<IndexableFileSet> getIndexableSets() {
1457     return myIndexableSets;
1458   }
1459
1460   @ApiStatus.Internal
1461   public void dropNontrivialIndexedStates(int inputId) {
1462     for (ID<?, ?> state : IndexingStamp.getNontrivialFileIndexedStates(inputId)) {
1463       getIndex(state).resetIndexedStateForFile(inputId);
1464     }
1465   }
1466
1467   void doTransientStateChangeForFile(int fileId, @NotNull VirtualFile file) {
1468     waitUntilIndicesAreInitialized();
1469     if (!clearUpToDateStateForPsiIndicesOfUnsavedDocuments(file, IndexingStamp.getNontrivialFileIndexedStates(fileId))) {
1470       // change in persistent file
1471       clearUpToDateStateForPsiIndicesOfVirtualFile(file);
1472     }
1473   }
1474
1475   void doInvalidateIndicesForFile(int fileId, @NotNull VirtualFile file, boolean contentChanged) {
1476     waitUntilIndicesAreInitialized();
1477     cleanProcessedFlag(file);
1478
1479     List<ID<?, ?>> nontrivialFileIndexedStates = IndexingStamp.getNontrivialFileIndexedStates(fileId);
1480     Collection<ID<?, ?>> fileIndexedStatesToUpdate = ContainerUtil.intersection(nontrivialFileIndexedStates, myRegisteredIndexes.getRequiringContentIndices());
1481
1482     // transient index value can depend on disk value because former is diff to latter
1483     // it doesn't matter content hanged or not: indices might depend on file name too
1484     removeTransientFileDataFromIndices(nontrivialFileIndexedStates, fileId, file);
1485
1486     if (contentChanged) {
1487       // only mark the file as outdated, reindex will be done lazily
1488       if (!fileIndexedStatesToUpdate.isEmpty()) {
1489         //noinspection ForLoopReplaceableByForEach
1490         for (int i = 0, size = nontrivialFileIndexedStates.size(); i < size; ++i) {
1491           final ID<?, ?> indexId = nontrivialFileIndexedStates.get(i);
1492           if (needsFileContentLoading(indexId)) {
1493             getIndex(indexId).resetIndexedStateForFile(fileId);
1494           }
1495         }
1496
1497         // the file is for sure not a dir and it was previously indexed by at least one index
1498         if (file.isValid()) {
1499           if (!isTooLarge(file)) {
1500             getChangedFilesCollector().scheduleForUpdate(file);
1501           }
1502           else getChangedFilesCollector().scheduleForUpdate(new DeletedVirtualFileStub((VirtualFileWithId)file));
1503         }
1504         else {
1505           LOG.info("Unexpected state in update:" + file);
1506         }
1507       }
1508     }
1509     else { // file was removed
1510       for (ID<?, ?> indexId : nontrivialFileIndexedStates) {
1511         if (!myRegisteredIndexes.isContentDependentIndex(indexId)) {
1512           updateSingleIndex(indexId, null, fileId, null);
1513         }
1514       }
1515       if (!fileIndexedStatesToUpdate.isEmpty()) {
1516         // its data should be (lazily) wiped for every index
1517         getChangedFilesCollector().scheduleForUpdate(new DeletedVirtualFileStub((VirtualFileWithId)file));
1518       }
1519       else {
1520         getChangedFilesCollector().removeScheduledFileFromUpdate(file); // no need to update it anymore
1521       }
1522     }
1523   }
1524
1525   void scheduleFileForIndexing(int fileId, @NotNull VirtualFile file, boolean contentChange) {
1526     final List<IndexableFilesFilter> filters = IndexableFilesFilter.EP_NAME.getExtensionList();
1527     if (!filters.isEmpty() && !ContainerUtil.exists(filters, e -> e.shouldIndex(file))) return;
1528
1529     // handle 'content-less' indices separately
1530     boolean fileIsDirectory = file.isDirectory();
1531
1532     if (!contentChange) {
1533       FileContent fileContent = null;
1534       for (ID<?, ?> indexId : getContentLessIndexes(fileIsDirectory)) {
1535         if (getInputFilter(indexId).acceptInput(file)) {
1536           if (fileContent == null) {
1537             fileContent = new IndexedFileWrapper(new IndexedFileImpl(file, null));
1538           }
1539           updateSingleIndex(indexId, file, fileId, fileContent);
1540         }
1541       }
1542     }
1543     // For 'normal indices' schedule the file for update and reset stamps for all affected indices (there
1544     // can be client that used indices between before and after events, in such case indices are up to date due to force update
1545     // with old content)
1546     if (!fileIsDirectory) {
1547       if (!file.isValid() || isTooLarge(file)) {
1548         // large file might be scheduled for update in before event when its size was not large
1549         getChangedFilesCollector().removeScheduledFileFromUpdate(file);
1550       }
1551       else {
1552         ourFileToBeIndexed.set(file);
1553         try {
1554           getFileTypeManager().freezeFileTypeTemporarilyIn(file, () -> {
1555             final List<ID<?, ?>> candidates = getAffectedIndexCandidates(file);
1556
1557             boolean scheduleForUpdate = false;
1558
1559             //noinspection ForLoopReplaceableByForEach
1560             for (int i = 0, size = candidates.size(); i < size; ++i) {
1561               final ID<?, ?> indexId = candidates.get(i);
1562               if (needsFileContentLoading(indexId) && getInputFilter(indexId).acceptInput(file)) {
1563                 getIndex(indexId).resetIndexedStateForFile(fileId);
1564                 scheduleForUpdate = true;
1565               }
1566             }
1567
1568             if (scheduleForUpdate) {
1569               IndexingStamp.flushCache(fileId);
1570               getChangedFilesCollector().scheduleForUpdate(file);
1571             }
1572             else if (file instanceof VirtualFileSystemEntry) {
1573               ((VirtualFileSystemEntry)file).setFileIndexed(true);
1574             }
1575           });
1576         } finally {
1577           ourFileToBeIndexed.remove();
1578         }
1579       }
1580     }
1581   }
1582
1583   @NotNull
1584   Collection<ID<?, ?>> getContentLessIndexes(boolean isDirectory) {
1585     return isDirectory ? myRegisteredIndexes.getIndicesForDirectories() : myRegisteredIndexes.getNotRequiringContentIndices();
1586   }
1587
1588   @NotNull
1589   public Collection<ID<?, ?>> getContentDependentIndexes() {
1590     return myRegisteredIndexes.getRequiringContentIndices();
1591   }
1592
1593   static FileTypeManagerImpl getFileTypeManager() {
1594     return (FileTypeManagerImpl)FileTypeManager.getInstance();
1595   }
1596
1597   private boolean clearUpToDateStateForPsiIndicesOfUnsavedDocuments(@NotNull VirtualFile file, Collection<? extends ID<?, ?>> affectedIndices) {
1598     clearUpToDateIndexesForUnsavedOrTransactedDocs();
1599
1600     Document document = myFileDocumentManager.getCachedDocument(file);
1601
1602     if (document != null && myFileDocumentManager.isDocumentUnsaved(document)) {   // will be reindexed in indexUnsavedDocuments
1603       myLastIndexedDocStamps.clearForDocument(document); // Q: non psi indices
1604       document.putUserData(ourFileContentKey, null);
1605
1606       return true;
1607     }
1608
1609     removeTransientFileDataFromIndices(ContainerUtil.intersection(affectedIndices, myRegisteredIndexes.getRequiringContentIndices()), getFileId(file), file);
1610     return false;
1611   }
1612
1613   void clearUpToDateIndexesForUnsavedOrTransactedDocs() {
1614     if (!myUpToDateIndicesForUnsavedOrTransactedDocuments.isEmpty()) {
1615       myUpToDateIndicesForUnsavedOrTransactedDocuments.clear();
1616     }
1617   }
1618
1619   static int getIdMaskingNonIdBasedFile(@NotNull VirtualFile file) {
1620     return file instanceof VirtualFileWithId ?((VirtualFileWithId)file).getId() : IndexingStamp.INVALID_FILE_ID;
1621   }
1622
1623   FileIndexingState shouldIndexFile(@NotNull IndexedFile file, @NotNull ID<?, ?> indexId) {
1624     if (!getInputFilter(indexId).acceptInput(file.getFile())) {
1625       return getIndexingState(file, indexId) == FileIndexingState.NOT_INDEXED
1626              ? FileIndexingState.UP_TO_DATE
1627              : FileIndexingState.OUT_DATED;
1628     }
1629     return getIndexingState(file, indexId);
1630   }
1631
1632   @NotNull
1633   private FileIndexingState getIndexingState(@NotNull IndexedFile file, @NotNull ID<?, ?> indexId) {
1634     VirtualFile virtualFile = file.getFile();
1635     if (isMock(virtualFile)) return FileIndexingState.NOT_INDEXED;
1636     return getIndex(indexId).getIndexingStateForFile(((NewVirtualFile)virtualFile).getId(), file);
1637   }
1638
1639   static boolean isMock(final VirtualFile file) {
1640     return !(file instanceof NewVirtualFile);
1641   }
1642
1643   public boolean isTooLarge(@NotNull VirtualFile file) {
1644     return isTooLarge(file, null);
1645   }
1646
1647   public boolean isTooLarge(@NotNull VirtualFile file,
1648                             @Nullable("if content size should be retrieved from a file") Long contentSize) {
1649     return isTooLarge(file, contentSize, myRegisteredIndexes.getNoLimitCheckFileTypes());
1650   }
1651
1652   @ApiStatus.Internal
1653   public static boolean isTooLarge(@NotNull VirtualFile file,
1654                                    @Nullable("if content size should be retrieved from a file") Long contentSize,
1655                                    @NotNull Set<FileType> noLimitFileTypes) {
1656     if (SingleRootFileViewProvider.isTooLargeForIntelligence(file, contentSize)) {
1657       return !noLimitFileTypes.contains(file.getFileType()) || SingleRootFileViewProvider.isTooLargeForContentLoading(file, contentSize);
1658     }
1659     return false;
1660   }
1661
1662   @Override
1663   public void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project) {
1664     myIndexableSets.add(set);
1665     myIndexableSetToProjectMap.put(set, project);
1666     if (project != null) {
1667       ((PsiManagerImpl)PsiManager.getInstance(project)).addTreeChangePreprocessor(event -> {
1668         if (event.isGenericChange() &&
1669             event.getCode() == PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED) {
1670           PsiFile file = event.getFile();
1671
1672           if (file != null) {
1673             VirtualFile virtualFile = file.getVirtualFile();
1674             if (virtualFile instanceof VirtualFileWithId && !isMock(virtualFile)) {
1675               getChangedFilesCollector().getEventMerger().recordTransientStateChangeEvent(virtualFile);
1676             }
1677           }
1678         }
1679       });
1680     }
1681   }
1682
1683   private void clearUpToDateStateForPsiIndicesOfVirtualFile(VirtualFile virtualFile) {
1684     if (virtualFile instanceof VirtualFileWithId) {
1685       int fileId = ((VirtualFileWithId)virtualFile).getId();
1686       boolean wasIndexed = false;
1687       List<ID<?, ?>> candidates = getAffectedIndexCandidates(virtualFile);
1688       for (ID<?, ?> candidate : candidates) {
1689         if (myRegisteredIndexes.isContentDependentIndex(candidate)) {
1690           if(getInputFilter(candidate).acceptInput(virtualFile)) {
1691             getIndex(candidate).resetIndexedStateForFile(fileId);
1692             wasIndexed = true;
1693           }
1694         }
1695       }
1696       if (wasIndexed) {
1697         getChangedFilesCollector().scheduleForUpdate(virtualFile);
1698         IndexingStamp.flushCache(fileId);
1699       }
1700     }
1701   }
1702
1703   @Override
1704   public void removeIndexableSet(@NotNull IndexableFileSet set) {
1705     if (!myIndexableSetToProjectMap.containsKey(set)) return;
1706     myIndexableSets.remove(set);
1707     myIndexableSetToProjectMap.remove(set);
1708
1709     ChangedFilesCollector changedFilesCollector = getChangedFilesCollector();
1710     for (VirtualFile file : changedFilesCollector.getAllFilesToUpdate()) {
1711       final int fileId = Math.abs(getIdMaskingNonIdBasedFile(file));
1712       if (!file.isValid()) {
1713         removeDataFromIndicesForFile(fileId, file);
1714         changedFilesCollector.removeFileIdFromFilesScheduledForUpdate(fileId);
1715       }
1716       else if (getIndexableSetForFile(file) == null) { // todo remove data from indices for removed
1717         changedFilesCollector.removeFileIdFromFilesScheduledForUpdate(fileId);
1718       }
1719     }
1720
1721     IndexingStamp.flushCaches();
1722   }
1723
1724   @Override
1725   public VirtualFile findFileById(Project project, int id) {
1726     return IndexInfrastructure.findFileById((PersistentFS)ManagingFS.getInstance(), id);
1727   }
1728
1729   @Nullable
1730   private static PsiFile findLatestKnownPsiForUncomittedDocument(@NotNull Document doc, @NotNull Project project) {
1731     return PsiDocumentManager.getInstance(project).getCachedPsiFile(doc);
1732   }
1733
1734   @VisibleForTesting
1735   public static void cleanupProcessedFlag() {
1736     final VirtualFile[] roots = ManagingFS.getInstance().getRoots();
1737     for (VirtualFile root : roots) {
1738       cleanProcessedFlag(root);
1739     }
1740   }
1741
1742   static void cleanProcessedFlag(@NotNull final VirtualFile file) {
1743     if (!(file instanceof VirtualFileSystemEntry)) return;
1744
1745     final VirtualFileSystemEntry nvf = (VirtualFileSystemEntry)file;
1746     nvf.setFileIndexed(false);
1747     if (file.isDirectory()) {
1748       for (VirtualFile child : nvf.getCachedChildren()) {
1749         cleanProcessedFlag(child);
1750       }
1751     }
1752   }
1753
1754   void setUpFlusher() {
1755     myFlushingFuture = FlushingDaemon.everyFiveSeconds(new Runnable() {
1756       private final SerializationManagerEx mySerializationManager = SerializationManagerEx.getInstanceEx();
1757       private int lastModCount;
1758
1759       @Override
1760       public void run() {
1761         mySerializationManager.flushNameStorage();
1762
1763         int currentModCount = myLocalModCount.get();
1764         if (lastModCount == currentModCount) {
1765           flushAllIndices(lastModCount);
1766         }
1767         lastModCount = currentModCount;
1768       }
1769     });
1770   }
1771
1772   @Override
1773   public void invalidateCaches() {
1774     CorruptionMarker.requestInvalidation();
1775   }
1776
1777   @Override
1778   @ApiStatus.Internal
1779   @NotNull
1780   public IntPredicate getAccessibleFileIdFilter(@Nullable Project project) {
1781     boolean dumb = ActionUtil.isDumbMode(project);
1782     if (!dumb) return f -> true;
1783
1784     DumbModeAccessType dumbModeAccessType = getCurrentDumbModeAccessType();
1785     if (dumbModeAccessType == null) {
1786       //throw new IllegalStateException("index access is not allowed in dumb mode");
1787       return __ -> true;
1788     }
1789
1790     if (dumbModeAccessType == DumbModeAccessType.RAW_INDEX_DATA_ACCEPTABLE) return f -> true;
1791
1792     assert dumbModeAccessType == DumbModeAccessType.RELIABLE_DATA_ONLY;
1793     return fileId -> !getChangedFilesCollector().containsFileId(fileId);
1794   }
1795
1796   @ApiStatus.Internal
1797   public void flushIndexes() {
1798     for (ID<?, ?> id : getRegisteredIndexes().getState().getIndexIDs()) {
1799       try {
1800         getIndex(id).flush();
1801       }
1802       catch (StorageException e) {
1803         throw new RuntimeException(e);
1804       }
1805     }
1806   }
1807
1808   public static final boolean DO_TRACE_STUB_INDEX_UPDATE = SystemProperties.getBooleanProperty("idea.trace.stub.index.update", false);
1809
1810   @ApiStatus.Internal
1811   static <K, V> int getIndexExtensionVersion(@NotNull FileBasedIndexExtension<K, V> extension) {
1812     int version = extension.getVersion();
1813
1814     if (VfsAwareMapReduceIndex.hasSnapshotMapping(extension)) {
1815       version += SnapshotInputMappings.getVersion();
1816     }
1817     return version;
1818   }
1819 }