@ApiStatus.Internal
  public synchronized void waitAllTasksExecuted(long timeout, @NotNull TimeUnit unit)
    throws ExecutionException, InterruptedException, TimeoutException {
    CountDownLatch started = new CountDownLatch(myMaxThreads);
    CountDownLatch readyToFinish = new CountDownLatch(1);
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        try {
          started.countDown();
          readyToFinish.await();
        }
        catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }

      @Override
      public String toString() {
        return "LastTask to waitAllTasksExecuted for " + timeout + " " + unit + " (" + System.identityHashCode(this) + ")";
      }
    };
    // Submit 'myMaxTasks' runnables and wait for them all to start.
    // They will spread to all executor threads and ensure the previously submitted tasks are completed.
    // Wait for all empty runnables to finish freeing up the threads.
    List<Future<?>> futures = ContainerUtil.map(Collections.nCopies(myMaxThreads, null), __ -> {
      LastTask wait = new LastTask(runnable);
      execute(wait);
      return wait;
    });
    long deadline = System.nanoTime() + unit.toNanos(timeout);
    try {
      if (!started.await(timeout, unit)) {
        throw new TimeoutException("Interrupted by timeout. " + this);
      }
    }
    catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    finally {
      readyToFinish.countDown();
    }
    ConcurrencyUtil.getAll(Math.max(0, deadline-System.nanoTime()), TimeUnit.NANOSECONDS, futures);
  }
@ApiStatus.Internal
  public synchronized void waitAllTasksExecuted(long timeout, @NotNull TimeUnit unit)