private @NotNull CompletableFuture<R> normalExec(@NotNull CompletableFuture<? extends @NotNull ProgressIndicator> progressFuture,
                                                   @NotNull Semaphore modalityEntered,
                                                   @NotNull Function<@NotNull ProgressIndicator, R> onThreadCallable) {

    if (isModal) {
      Function<ProgressIndicator, ProgressIndicator> modalityRunnable = progressIndicator -> {
        LaterInvocator.enterModal(progressIndicator, (ModalityStateEx)progressIndicator.getModalityState());
        modalityEntered.up();
        return progressIndicator;
      };
      // If a progress indicator has not been calculated yet, grabbing IW lock might lead to deadlock, as progress might need it for init
      progressFuture = progressFuture.thenApplyAsync(modalityRunnable, r -> {
        if (ApplicationManager.getApplication().isWriteIntentLockAcquired()) {
          r.run();
        }
        else {
          ApplicationManager.getApplication().invokeLaterOnWriteThread(r);
        }
      });
    }

    CompletableFuture<R> resultFuture = launchTask(onThreadCallable, progressFuture);

    if (isModal) {
      CompletableFuture<Void> modalityExitFuture = resultFuture
        .handle((r, throwable) -> r) // ignore result computation exception
        .thenAcceptBoth(progressFuture, (r, progressIndicator) -> {
          if (ApplicationManager.getApplication().isWriteIntentLockAcquired()) {
            LaterInvocator.leaveModal(progressIndicator);
          }
          else {
            ApplicationManager.getApplication()
              .invokeLaterOnWriteThread(() -> LaterInvocator.leaveModal(progressIndicator), progressIndicator.getModalityState());
          }
        });

      // It's better to associate task future with modality exit so that future finish will lead to expected state (modality exit)
      resultFuture = resultFuture.thenCombine(modalityExitFuture, (r, __) -> r);
    }

    if (isSync) {
      waitForFutureUnlockingThread(resultFuture);
    }
    return resultFuture;
  }
private @NotNull CompletableFuture<R> normalExec(@NotNull CompletableFuture<? extends @NotNull ProgressIndicator> progressFuture,
                                                   @NotNull Semaphore modalityEntered,
                                                   @NotNull Function<@NotNull ProgressIndicator, R> onThreadCallable)