void performShutdown(boolean keepConnection, @NotNull String reason) {
    myShutdownReason = keepConnection ? reason : null;
    RegisteredIndexes registeredIndexes = myRegisteredIndexes;
    if (registeredIndexes == null || !registeredIndexes.performShutdown()) {
      return; // already shut down
    }

    ProgressManager.getInstance().executeNonCancelableSection(() -> {
      registeredIndexes.waitUntilAllIndicesAreInitialized();
    });
    try {
      if (myShutDownTask != null) {
        ShutDownTracker.getInstance().unregisterShutdownTask(myShutDownTask);
      }
      if (myFlushingTask != null) {
        try {
          myFlushingTask.close();
        }
        catch (Exception e) {
          LOG.error("Error cancelling flushing task", e);
        }
        myFlushingTask = null;
      }
    }
    finally {
      long ms = System.currentTimeMillis();
      LOG.info("Index dispose started");
      try {
        PersistentIndicesConfiguration.saveConfiguration();

        IntSet unprocessedOrphanDirtyFiles = new IntOpenHashSet();
        OrphanDirtyFilesQueue orphanDirtyFileIds = registeredIndexes.getOrphanDirtyFilesQueue();
        unprocessedOrphanDirtyFiles.addAll(orphanDirtyFileIds.getFileIds());

        if (myIsUnitTestMode) {
          IntSet allStaleIdsToCheck = new IntOpenHashSet();
          allStaleIdsToCheck.addAll(unprocessedOrphanDirtyFiles);
          synchronized (myStaleIds) {
            allStaleIdsToCheck.addAll(myStaleIds);
            myStaleIds.clear();
          }
          // project dirty files are still in ChangedFilesCollector in case FileBasedIndex is restarted using Tumbler (projects are not closed)
          // we need to persist queues, so we can properly re-read them here and in FileBasedIndexDataInitialization
          //noinspection TestOnlyProblems
          persistProjectsDirtyFiles();
          // some deleted files were already saved to project queues and saved to disk during project closing
          // we need to read them to avoid false-positive errors in checkIndexForStaleRecords
          readAllProjectDirtyFilesQueues(allStaleIdsToCheck);

          allStaleIdsToCheck.addAll(getAllDirtyFiles(null));

          UpdatableIndex<Integer, SerializedStubTree, FileContent, ?> index = getState().getIndex(StubUpdatingIndex.INDEX_ID);
          if (index != null) {
            StaleIndexesChecker.checkIndexForStaleRecords(index, allStaleIdsToCheck, false);
          }
        }

        IntSet orphanDirtyFilesFromThisSession = getAllDirtyFiles(null);
        int maxSize = Registry.intValue("maximum.size.of.orphan.dirty.files.queue");
        orphanDirtyFileIds.plus(orphanDirtyFilesFromThisSession).takeLast(maxSize).store(vfsCreationStamp);
        // remove events from event merger, so they don't show up after FileBasedIndex is restarted using tumbler
        getChangedFilesCollector().clear();
        myFilesToUpdateCollector.clear();
        myDirtyFiles.clear();
        vfsCreationStamp = 0;

        // TODO-ank: Should we catch and ignore CancellationException here to allow other lines to execute?
        IndexingStamp.close();
        IndexingFlag.unlockAllFiles(); // TODO-ank: IndexingFlag should also be closed, because indexes might be cleared (IDEA-336540)
        // TODO-ank: review all the remaining usages of fast file attributes (IDEA-336540)

        List<ThrowableRunnable<?>> indexDisposeTasks = new ArrayList<>();
        IndexConfiguration state = getState();
        for (ID<?, ?> indexId : state.getIndexIDs()) {
          PingProgress.interactWithEdtProgress();
          indexDisposeTasks.add(() -> {
            try {
              UpdatableIndex<?, ?, FileContent, ?> index = getIndex(indexId);
              if (!RebuildStatus.isOk(indexId)) {
                clearIndex(indexId); // if the index was scheduled for rebuild, only clean it
              }
              index.dispose();
            }
            catch (Throwable throwable) {
              LOG.info("Problem disposing " + indexId, throwable);
            }
          });
        }

        IndexDataInitializer.runParallelTasks(indexDisposeTasks, false);
        FileBasedIndexInfrastructureExtension.EP_NAME.getExtensionList().forEach(ex -> ex.shutdown());
        if (!keepConnection) {
          myConnection.disconnect();
        }
        //CorruptionMarker.markIndexesAsClosed();
      }
      catch (Throwable e) {
        LOG.error("Problems during index shutdown", e);
      }
      finally {
        IndexVersion.clearCachedIndexVersions();
      }
      LOG.info("Index dispose completed in " + (System.currentTimeMillis() - ms) + "ms.");
    }
  }
void performShutdown(boolean keepConnection, @NotNull String reason)