private void asyncRecoverLeftTsFiles() {
    if (absolutePath2RecoverPerformer.isEmpty()) {
      return;
    }

    List<Future<Void>> futures = new ArrayList<>();
    ExecutorService recoverTsFilesThreadPool =
        IoTDBThreadPoolFactory.newFixedThreadPool(
            Runtime.getRuntime().availableProcessors(), ThreadName.TSFILE_RECOVER.getName());
    // async recover
    for (UnsealedTsFileRecoverPerformer recoverPerformer : absolutePath2RecoverPerformer.values()) {
      Callable<Void> recoverTsFileTask =
          () -> {
            try {
              recoverPerformer.startRecovery();
              // skip redo logs because it doesn't belong to any wal node
              recoverPerformer.endRecovery();
              recoverPerformer.getRecoverListener().succeed();
            } catch (DataRegionException | IOException | WALRecoverException e) {
              logger.error(
                  "Fail to recover unsealed TsFile {}, skip it.",
                  recoverPerformer.getTsFileAbsolutePath(),
                  e);
              recoverPerformer.getRecoverListener().fail(e);
            }
            return null;
          };
      futures.add(recoverTsFilesThreadPool.submit(recoverTsFileTask));
    }
    // wait until all tasks done
    for (Future<Void> future : futures) {
      try {
        future.get();
      } catch (ExecutionException e) {
        throw new StorageEngineFailureException("StorageEngine failed to recover.", e);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new StorageEngineFailureException("StorageEngine failed to recover.", e);
      }
    }
    recoverTsFilesThreadPool.shutdown();
  }
First set allVsgScannedLatch, then call recover method.
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/WALRecoverManager.java