@VisibleForTesting
  /** Assumes the epic lock is already taken. */
  void tryScheduleUnderLock(final TaskWrapper taskWrapper) throws RejectedExecutionException {
    LOG.info("Attempting to execute {}", taskWrapper);
    TaskRunnerCallable task = taskWrapper.getTaskRunnerCallable();
    ListenableFuture<TaskRunner2Result> future = executorService.submit(task);
    task.setWmCountersRunning();
    runningFragmentCount.incrementAndGet();
    taskWrapper.setIsInWaitQueue(false);

    FutureCallback<TaskRunner2Result> wrappedCallback = createInternalCompletionListener(
      taskWrapper);
    // Callback on a separate thread so that when a task completes, the thread in the main queue
    // is actually available for execution and will not potentially result in a RejectedExecution
    Futures.addCallback(future, wrappedCallback, executionCompletionExecutorService);

    boolean canFinish = taskWrapper.getTaskRunnerCallable().canFinish(),
        isGuaranteed = taskWrapper.isGuaranteed();
    if (LOG.isDebugEnabled()) {
      LOG.debug("{} scheduled for execution. canFinish={}, isGuaranteed={}",
          taskWrapper.getRequestId(), canFinish, isGuaranteed);
    }

    // only tasks that cannot finish immediately are pre-emptable. In other words, if all inputs
    // to the tasks are not ready yet, the task is eligible for pre-emptable.
    if (enablePreemption) {
      if (!canFinish || !isGuaranteed) {
        LOG.info("Adding {} to pre-emption queue", taskWrapper.getRequestId());
        addToPreemptionQueue(taskWrapper);
      }
    }
    numSlotsAvailable.decrementAndGet();
    metrics.setNumExecutorsAvailable(numSlotsAvailable.get());
    // Wait queue could have been re-ordered in the mean time because of concurrent task
    // submission. So remove the specific task instead of the head task.
    if (waitQueue.remove(taskWrapper)) {
      metrics.setExecutorNumQueuedRequests(waitQueue.size());
    }
  }
A map where fragments live between when the TezTaskRunner2 calls taskSucceeded/Failed, or
 an external caller calls killTask, and when the TaskRunnerCallable comes out of the
 threadpool.
llap-server/src/java/org/apache/hadoop/hive/llap/daemon/impl/TaskExecutorService.java