@Override
  public <K> boolean processAllKeys(@NotNull ID<K, ?> indexId,
                                    @NotNull Processor<? super K> processor,
                                    @NotNull GlobalSearchScope scope,
                                    @Nullable IdFilter idFilter) {
    var trace = lookupAllKeysStarted(indexId)
      .withProject(scope.getProject());
    try {
      waitUntilIndicesAreInitialized();
      UpdatableIndex<K, ?, FileContent, ?> index = getIndex(indexId);
      if (!ensureUpToDate(indexId, scope.getProject(), scope, null)) {
        return true;
      }

      trace.indexValidationFinished();

      IdFilter idFilterAdjusted = idFilter == null ? extractIdFilter(scope, scope.getProject()) : idFilter;
      trace.totalKeysIndexed(keysCountApproximatelyIfPossible(index));
      return index.processAllKeys(processor, scope, idFilterAdjusted);
    }
    catch (StorageException e) {
      trace.lookupFailed();
      requestRebuild(indexId, e);
    }
    catch (RuntimeException e) {
      trace.lookupFailed();
      final Throwable cause = e.getCause();
      if (cause instanceof StorageException || cause instanceof IOException) {
        requestRebuild(indexId, cause);
      }
      else {
        throw e;
      }
    }
    finally {
      //Not using try-with-resources because in case of exceptions are thrown, .close() needs to be called _after_ catch,
      //  so .lookupFailed() is invoked on a not-yet-closed trace -- but TWR does the opposite: first close resources, then
      //  do all catch/finally blocks
      trace.close();
    }

    return false;
  }
@Override
  public <K> boolean processAllKeys(@NotNull ID<K, ?> indexId,
                                    @NotNull Processor<? super K> processor,
                                    @NotNull GlobalSearchScope scope,
                                    @Nullable IdFilter idFilter)