private static void doDelete(Path path) throws IOException {
    for (int attemptsLeft = MAX_FILE_IO_ATTEMPTS; attemptsLeft > 0; attemptsLeft--) {
      try {
        Files.deleteIfExists(path);
        return;
      }
      catch (IOException e) {
        if (!SystemInfoRt.isWindows || attemptsLeft == 1) {
          //noinspection InstanceofCatchParameter
          if (e instanceof DirectoryNotEmptyException) {
            //add the directory content to the exception:
            DirectoryNotEmptyException replacingEx = directoryNotEmptyExceptionWithMoreDiagnostic(path);
            replacingEx.addSuppressed(e);
            throw replacingEx;
          }
          throw e;
        }

        //noinspection InstanceofCatchParameter
        if (e instanceof AccessDeniedException) {
          // a file could be read-only, then fallback to legacy java.io API helps
          try {
            File file = path.toFile();
            if (file.delete() || !file.exists()) {
              break;
            }
          }
          catch (Throwable ignored) { }

          if (attemptsLeft == MAX_FILE_IO_ATTEMPTS / 2 && TRY_GC_IF_FILE_DELETE_FAILS) {
            //Non-closed stream/channel, or not-yet-unmapped memory-mapped buffer could be a reason for
            // AccessDeniedException on an attempt to delete file on Windows.
            // => kick in GC/finalizers to collect that is not yet collected.
            //
            // Those are quite heavy, system-wide operations, which is why we fall back to them only after
            // several attempts to delete the file already failed. But we don't do it at the last attempt
            // either, because GC/finalizers tasks could run async/background and may need some time to
            // finish.

            //noinspection CallToSystemGC
            System.gc();
            System.runFinalization();
          }
        }
      }

      try { Thread.sleep(10); }
      catch (InterruptedException ignored) { }
    }
  }
private static void doDelete(Path path)