private boolean processFilesConcurrently(
      @NotNull Set<VirtualFile> files,
      @NotNull final ProgressIndicator indicator,
      @NotNull final Processor<VirtualFile> processor) {
    final List<VirtualFile> fileList = new ArrayList<VirtualFile>(files);
    // fine but grabs all CPUs
    // return JobLauncher.getInstance().invokeConcurrentlyUnderProgress(fileList, indicator, false,
    // false, processor);

    int parallelism = CacheUpdateRunner.indexingThreadCount();
    final Callable<Boolean> processFileFromSet =
        () -> {
          final boolean[] result = {true};
          ProgressManager.getInstance()
              .executeProcessUnderProgress(
                  () -> {
                    while (true) {
                      ProgressManager.checkCanceled();
                      VirtualFile file;
                      synchronized (fileList) {
                        file = fileList.isEmpty() ? null : fileList.remove(fileList.size() - 1);
                      }
                      if (file == null) {
                        break;
                      }
                      if (!processor.process(file)) {
                        result[0] = false;
                        break;
                      }
                    }
                  },
                  indicator);
          return result[0];
        };
    List<Future<Boolean>> futures =
        ContainerUtil.map(
            Collections.nCopies(parallelism, ""),
            s -> myApplication.executeOnPooledThread(processFileFromSet));

    List<Boolean> results =
        ContainerUtil.map(
            futures,
            future -> {
              try {
                return future.get();
              } catch (Exception e) {
                LOG.error(e);
              }
              return false;
            });

    return !ContainerUtil.exists(
        results,
        result -> {
          return result != null && !result; // null means PCE
        });
  }
  @Override
  public void saveDocument(@NotNull final Document document) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    if (!myUnsavedDocuments.contains(document)) return;

    try {
      doSaveDocument(document);
    } catch (IOException e) {
      handleErrorsOnSave(Collections.singletonMap(document, e));
    } catch (SaveVetoException ignored) {
    }
  }
  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();
          }
        }
      }
    }
  }