@NotNull
 private Set<VirtualFile> pollFilesToResolve() {
   Set<VirtualFile> set;
   synchronized (filesToResolve) {
     int queuedSize = filesToResolve.size();
     set = new LinkedHashSet<VirtualFile>(queuedSize);
     // someone might have cleared this bit to mark file as processed
     for (VirtualFile file : filesToResolve) {
       if (fileIsInQueue.clear(getAbsId(file))) {
         set.add(file);
       }
     }
     filesToResolve.clear();
   }
   return countAndMarkUnresolved(set, false);
 }
  @Override
  public void run() {
    while (!myDisposed) {
      boolean isEmpty;
      synchronized (filesToResolve) {
        isEmpty = filesToResolve.isEmpty();
      }
      if (enableVetoes.get() > 0
          || isEmpty
          || !resolveProcess.isDone()
          || HeavyProcessLatch.INSTANCE.isRunning()
          || PsiDocumentManager.getInstance(myProject).hasUncommitedDocuments()) {
        try {
          waitForQueue();
        } catch (InterruptedException e) {
          break;
        }
        continue;
      }
      final Set<VirtualFile> files = pollFilesToResolve();
      if (files.isEmpty()) continue;

      upToDate = false;

      myApplication.invokeLater(
          () -> {
            if (!resolveProcess.isDone()) return;
            log("Started to resolve " + files.size() + " files");

            Task.Backgroundable backgroundable =
                new Task.Backgroundable(myProject, "Resolving files...", false) {
                  @Override
                  public void run(@NotNull final ProgressIndicator indicator) {
                    if (!myApplication.isDisposed()) {
                      processBatch(indicator, files);
                    }
                  }
                };
            ProgressIndicator indicator;
            if (files.size() > 1) {
              // show progress
              indicator = new BackgroundableProcessIndicator(backgroundable);
            } else {
              indicator = new MyProgress();
            }
            resolveProcess =
                ((ProgressManagerImpl) ProgressManager.getInstance())
                    .runProcessWithProgressAsynchronously(backgroundable, indicator, null);
          },
          myProject.getDisposed());

      flushLog();
    }
  }
 // return true if file was added to queue
 private boolean queueUpdate(@NotNull VirtualFile file) {
   synchronized (filesToResolve) {
     if (!(file instanceof VirtualFileWithId)) return false;
     int fileId = getAbsId(file);
     countAndMarkUnresolved(file, new LinkedHashSet<VirtualFile>(), true);
     boolean alreadyAdded = fileIsInQueue.set(fileId);
     if (!alreadyAdded) {
       filesToResolve.add(file);
     }
     upToDate = false;
     wakeUpUnderLock();
     return !alreadyAdded;
   }
 }
 @Override
 public int getQueueSize() {
   synchronized (filesToResolve) {
     return filesToResolve.size();
   }
 }
  private void processBatch(
      @NotNull final ProgressIndicator indicator, @NotNull Set<VirtualFile> files) {
    assert !myApplication.isDispatchThread();
    final int resolvedInPreviousBatch = this.resolvedInPreviousBatch;
    final int totalSize = files.size() + resolvedInPreviousBatch;
    final ConcurrentIntObjectMap<int[]> fileToForwardIds =
        ContainerUtil.createConcurrentIntObjectMap();
    final Set<VirtualFile> toProcess = Collections.synchronizedSet(files);
    indicator.setIndeterminate(false);
    ProgressIndicatorUtils.forceWriteActionPriority(indicator, (Disposable) indicator);
    long start = System.currentTimeMillis();
    Processor<VirtualFile> processor =
        file -> {
          double fraction = 1 - toProcess.size() * 1.0 / totalSize;
          indicator.setFraction(fraction);
          try {
            if (!file.isDirectory() && toResolve(file, myProject)) {
              int fileId = getAbsId(file);
              int i = totalSize - toProcess.size();
              indicator.setText(i + "/" + totalSize + ": Resolving " + file.getPresentableUrl());
              int[] forwardIds = processFile(file, fileId, indicator);
              if (forwardIds == null) {
                // queueUpdate(file);
                return false;
              }
              fileToForwardIds.put(fileId, forwardIds);
            }
            toProcess.remove(file);
            return true;
          } catch (RuntimeException e) {
            indicator.checkCanceled();
          }
          return true;
        };
    boolean success = true;
    try {
      success = processFilesConcurrently(files, indicator, processor);
    } finally {
      this.resolvedInPreviousBatch = toProcess.isEmpty() ? 0 : totalSize - toProcess.size();
      queue(toProcess, "re-added after fail. success=" + success);
      storeIds(fileToForwardIds);

      long end = System.currentTimeMillis();
      log(
          "Resolved batch of "
              + (totalSize - toProcess.size())
              + " from "
              + totalSize
              + " files in "
              + ((end - start) / 1000)
              + "sec. (Gap: "
              + storage.gap
              + ")");
      synchronized (filesToResolve) {
        upToDate = filesToResolve.isEmpty();
        log("upToDate = " + upToDate);
        if (upToDate) {
          for (Listener listener : myListeners) {
            listener.allFilesResolved();
          }
        }
      }
    }
  }
 private void waitForQueue() throws InterruptedException {
   synchronized (filesToResolve) {
     filesToResolve.wait(1000);
   }
 }
 private void wakeUpUnderLock() {
   filesToResolve.notifyAll();
 }