@NotNull Pair<List<String>, List<String>> getCanonicalWatchRoots() {
    initializeMappings();

    var canonicalPathMappings = new ConcurrentHashMap<String, String>();
    var futures = Stream.concat(myOptimizedRecursiveWatchRoots.stream(), myOptimizedFlatWatchRoots.stream())
      .map(root -> CompletableFuture.runAsync(() -> {
        var canonicalRoot = FileSystemUtil.resolveSymLink(root);
        if (canonicalRoot != null && OSAgnosticPathUtil.COMPARATOR.compare(canonicalRoot, root) != 0) {
          canonicalPathMappings.put(root, canonicalRoot);
        }
      }, ourExecutor))
      .toArray(CompletableFuture[]::new);
    CompletableFuture.allOf(futures).join();

    var canonicalRecursiveRoots = WatchRootsUtil.createFileNavigableSet();
    for (var root : myOptimizedRecursiveWatchRoots) {
      var canonical = canonicalPathMappings.get(root);
      if (canonical != null) {
        myPathMappings.putValue(canonical, root);
      }
      else {
        canonical = root;
      }
      WatchRootsUtil.insertRecursivePath(canonicalRecursiveRoots, canonical);
    }

    var canonicalFlatRoots = new HashSet<String>();
    for (var root : myOptimizedFlatWatchRoots) {
      var canonical = canonicalPathMappings.get(root);
      if (canonical != null) {
        myPathMappings.putValue(canonical, root);
      }
      else {
        canonical = root;
      }
      if (!WatchRootsUtil.isCoveredRecursively(canonicalRecursiveRoots, canonical)) {
        canonicalFlatRoots.add(canonical);
      }
    }

    return new Pair<>(new ArrayList<>(canonicalRecursiveRoots), new ArrayList<>(canonicalFlatRoots));
  }
@NotNull Pair<List<String>, List<String>> getCanonicalWatchRoots()