@VisibleForTesting
  static @NotNull PersistentFSConnection tryInit(@NotNull Path cachesDir,
                                                 int currentImplVersion,
                                                 boolean enableVfsLog,
                                                 @NotNull List<ConnectionInterceptor> interceptors,
                                                 @NotNull List<VFSRecoverer> recoverers,
                                                 boolean allowRecoveryFromVfsLog) throws IOException {
    //RC: Mental model behind VFS initialization:
    //   VFS consists of few different storages: records, attributes, content... Each storage has its own on-disk
    //   data format. Each storage also has a writeable header field .version, which is (must be) 0 by default.
    //   VFS as a whole defines 'implementation version' (currentImplVersion) which is the overall on-disk data
    //   format known to current VFS code.
    //
    //   When VFS is initialized from scratch, this 'VFS impl version' is stamped into each storage .version header field.
    //   When VFS is loaded (from already existing on-disk representation), we load each storage from apt file(s), collect
    //   all the storage.versions and check them against currentImplVersion -- and if all the versions are equal to
    //   currentImplVersion => VFS is OK and ready to use ('success path')
    //
    //   If not all versions are equal to currentImplVersion => we assume VFS is outdated, drop all the VFS files, and throw
    //   exception, which most likely triggers VFS rebuild from 0 somewhere above.
    //   Now, versions could be != currentImplVersion for different reasons:
    //   1) On-disk VFS are of an ancient version -- the most obvious reason
    //   2) Some of the VFS storages are missed on disk, and initialized from 0, thus having their .version=0
    //   3) Some of the VFS storages have an unrecognizable on-disk format (e.g. corrupted) -- in this case
    //      specific storage implementation could either
    //      a) re-create storage from 0 => will have .version=0, see branch #2
    //      b) throw an exception => will be caught, and VFS files will be all dropped,
    //                               and likely lead to VFS rebuild from 0 somewhere upper the callstack
    //   So the simple condition (all storages .version must be == currentImplVersion) actually covers a set of different cases
    //   which is convenient.
    //
    //MAYBE RC: Current model has important drawback: it is all-or-nothing, i.e. even a minor change in VFS on-disk format requires
    //          full VFS rebuild. VFS rebuild itself is not so costly -- but it invalidates all fileIds, which causes Indexes rebuild,
    //          which IS costly.
    //          It would be nicer if VFS be able to just 'upgrade' a minor change in format to a newer version, without full rebuild
    //          -- but with the current approach such functionality, it is hard to plug in.
    //          Sketch of a better implementation:
    //          1) VFS currentImplVersion is stored in a single dedicated place, in 'version.txt' file (human-readable)
    //          2) Each VFS storage manages its own on-disk-format-version -- i.e. each storage has its own CURRENT_IMPL_VERSION,
    //             which it stores somewhere in a file(s) header. And each storage is responsible for detecting on-disk version,
    //             and either read the known format, or read-and-silently-upgrade the known but slightly outdated format, or throw
    //             an error if it can't deal with on-disk data at all.
    //          VFS.currentImplVersion is changed only on a major format changes, there implement 'upgrade' is too costly, and full
    //          rebuild is the way to go. Another reason for full rebuild is if any of storages is completely confused by on-disk
    //          data (=likely corruption or too old data format). In other cases, VFS could be upgraded 'under the carpet' without
    //          invalidating fileId. Hence, Indexes don't need to be rebuilt.

    Path basePath = cachesDir.toAbsolutePath();
    Files.createDirectories(basePath);

    //MAYBE RC: looks like it all could be much easier with coroutines
    VFSAsyncTaskExecutor pool = PersistentFsConnectorHelper.INSTANCE.executor();

    PersistentFSPaths persistentFSPaths = new PersistentFSPaths(cachesDir);
    PersistentFSLoader vfsLoader = new PersistentFSLoader(persistentFSPaths, enableVfsLog, pool);
    try {
      if (VfsLog.isVfsTrackingEnabled() && enableVfsLog) {
        vfsLoader.replaceStoragesIfMarkerPresent();
      }
      vfsLoader.failIfCorruptionMarkerPresent();

      vfsLoader.initializeStorages();

      vfsLoader.ensureStoragesVersionsAreConsistent(currentImplVersion);

      boolean needInitialization = vfsLoader.isJustCreated();

      if (needInitialization) {
        // Create root record:
        int rootRecordId = vfsLoader.recordsStorage().allocateRecord();
        if (rootRecordId != FSRecords.ROOT_FILE_ID) {
          throw new AssertionError("First record created must have id=" + FSRecords.ROOT_FILE_ID + " but " + rootRecordId + " got instead");
        }
        vfsLoader.recordsStorage().cleanRecord(rootRecordId);
      }
      else {
        vfsLoader.selfCheck();

        if (!vfsLoader.problemsDuringLoad().isEmpty()) {
          for (VFSRecoverer recoverer : recoverers) {
            recoverer.tryRecover(vfsLoader);
          }

          //Were all problems recovered? -> fail if not
          List<VFSInitException> problemsNotRecovered = vfsLoader.problemsDuringLoad();
          if (!problemsNotRecovered.isEmpty()) {
            VFSInitException mainEx = problemsNotRecovered.get(0);
            for (int i = 1; i < problemsNotRecovered.size(); i++) {
              mainEx.addSuppressed(problemsNotRecovered.get(i));
            }

            if (allowRecoveryFromVfsLog && VfsLog.isVfsTrackingEnabled() && enableVfsLog) {
              // if vfslog is enabled, try to recover caches from the operations log first.
              // exception will be rethrown, but we won't delete the files and will just retry
              // initialization on the next attempt with substituted storages
              boolean cachesWereRecoveredFromLog = false;
              try {
                cachesWereRecoveredFromLog = vfsLoader.recoverCachesFromVfsLog();
              }
              catch (Throwable recoveryEx) {
                LOG.error("Cache recovery attempt via VfsLog has failed", recoveryEx);
              }
              if (cachesWereRecoveredFromLog) {
                throw new CachesWereRecoveredFromLogException(mainEx);
              }
            }

            throw mainEx;
          }
        }
      }
      // TODO maybe delete recovery-in-progress marker here. it can possibly survive to this point and break something in the future.

      PersistentFSConnection connection = vfsLoader.createConnection(interceptors);

      if (needInitialization) {//make just-initialized connection dirty (i.e. it must be saved)
        connection.markDirty();
      }

      return connection;
    }
    catch (Throwable e) { // IOException, IllegalArgumentException, AssertionError
      String errorMessage = ExceptionUtil.getNonEmptyMessage(e, "<unrecognized>");
      LOG.warn("Filesystem storage is corrupted or does not exist. [Re]Building. Reason: " + errorMessage);
      try {
        vfsLoader.closeEverything();

        List<StorageAlreadyInUseException> storageAlreadyInUseExceptions = findCauseAndSuppressed(e, StorageAlreadyInUseException.class);
        if(!storageAlreadyInUseExceptions.isEmpty()){
          //some of the storages are used by another process: don't clean VFS (it doesn't help), interrupt startup instead
          throw new IOException("Some of VFS storages are already in use: is an IDE process already running?", e);
        }

        if (e instanceof CachesWereRecoveredFromLogException) {
          // don't delete the caches, they will be substituted on the next attempt
          throw new VFSInitException(RECOVERED_FROM_LOG, errorMessage, e);
        }

        vfsLoader.deleteEverything();
      }
      catch (VFSInitException recoveredFromLogException) {
        throw recoveredFromLogException;
      }
      catch (IOException cleanEx) {
        e.addSuppressed(cleanEx);
        LOG.warn("Cannot clean filesystem storage", cleanEx);
      }

      //Try to unwrap exception, so the real cause appears, we could throw VFSNeedsRebuildException with it:

      List<VFSInitException> vfsNeedsRebuildExceptions = findCauseAndSuppressed(e, VFSInitException.class);
      if (!vfsNeedsRebuildExceptions.isEmpty()) {
        VFSInitException mainEx = vfsNeedsRebuildExceptions.get(0);
        for (VFSInitException suppressed : vfsNeedsRebuildExceptions.subList(1, vfsNeedsRebuildExceptions.size())) {
          mainEx.addSuppressed(suppressed);
        }
        throw mainEx;
      }

      //VersionUpdatedException extends CorruptedException, so we must look for VersionUpdated first:
      if (!findCauseAndSuppressed(e, VersionUpdatedException.class).isEmpty()) {
        throw new VFSInitException(IMPL_VERSION_MISMATCH, "Some of storages versions were changed", e);
      }

      if (!findCauseAndSuppressed(e, CorruptedException.class).isEmpty()) {
        //'not closed properly' is the most likely explanation of corrupted enumerator -- but not the only one,
        // it could also be a code bug
        throw new VFSInitException(NOT_CLOSED_PROPERLY, "Some of storages were corrupted", e);
      }

      throw new VFSInitException(UNRECOGNIZED, "VFS init failure of unrecognized category: " + errorMessage, e);
    }
  }
@VisibleForTesting
  static @NotNull PersistentFSConnection tryInit(@NotNull Path cachesDir,
                                                 int currentImplVersion,
                                                 boolean enableVfsLog,
                                                 @NotNull List<ConnectionInterceptor> interceptors,
                                                 @NotNull List<VFSRecoverer> recoverers,
                                                 boolean allowRecoveryFromVfsLog)