/**
  * Get job information
  *
  * @param id
  * @return
  * @throws NoSuchElementException if job with specified <code>id</code> does not exist
  */
 public JobInfo getJobInfo(int id) throws NoSuchElementException {
   PrioritizedRequest request = _jobs.get(id);
   if (request == null) {
     throw new NoSuchElementException("Job not found : Job-" + id);
   }
   return request.toJobInfo();
 }
 /**
  * Cancel the request. Any IO in progress will be interrupted.
  *
  * @param id
  * @param explanation A reason to log
  * @throws NoSuchElementException
  */
 public synchronized void cancel(int id, @Nullable String explanation)
     throws NoSuchElementException {
   PrioritizedRequest request = _jobs.get(id);
   if (request == null) {
     throw new NoSuchElementException("Job " + id + " not found");
   }
   request.kill(explanation);
   if (_queue.remove(request)) {
     postprocessWithoutJobSlot(request);
   }
 }
  /**
   * Add a request to the scheduler.
   *
   * <p>Returns true if the caller acquired a job slot and must send the job to execution.
   *
   * @param request
   * @return
   */
  private synchronized boolean submit(PrioritizedRequest request) {
    if (_jobs.put(request.getId(), request) != null) {
      throw new RuntimeException(
          "Duplicate mover id detected. Please report to [email protected].");
    }

    if (_semaphore.tryAcquire()) {
      return true;
    } else {
      _queue.add(request);
      return false;
    }
  }
  /**
   * Get mover id for given door request. If there is no mover associated with {@code
   * doorUniqueueRequest} a new mover will be created by using provided {@code moverSupplier}.
   *
   * <p>The returned mover id generated with following encoding: | 31- queue id -24|23- job id -0|
   *
   * @param moverSupplier {@link MoverSupplier} which can create a mover for given requests.
   * @param doorUniqueId unique request identifier generated by the door.
   * @param priority
   * @return mover id
   */
  public int getOrCreateMover(MoverSupplier moverSupplier, String doorUniqueId, IoPriority priority)
      throws CacheException {
    checkState(!_isShutdown);

    try {
      /* Create the request if it doesn't already exists.
       */
      PrioritizedRequest request =
          _moverByRequests.computeIfAbsent(
              doorUniqueId,
              key -> {
                try {
                  return createRequest(moverSupplier, key, priority);
                } catch (CacheException e) {
                  throw new RuntimeException(e);
                }
              });

      /* If not already queued, submit it.
       */
      if (request.queue()) {
        if (submit(request)) {
          /* There was a free slot in the queue so we submit directly to execution.
           */
          sendToExecution(request);
        } else if (_semaphore.getMaxPermits() <= 0) {
          LOGGER.warn(
              "A task was added to queue '{}', however the queue is not "
                  + "configured to execute any tasks.",
              _name);
        }
      }

      return request.getId();
    } catch (RuntimeException e) {
      Throwables.propagateIfInstanceOf(e.getCause(), CacheException.class);
      throw e;
    }
  }
  private void postprocessWithoutJobSlot(PrioritizedRequest request) {
    try (CDC ignore = request.getCdc().restore()) {
      request
          .getMover()
          .close(
              new CompletionHandler<Void, Void>() {
                @Override
                public void completed(Void result, Void attachment) {
                  release();
                }

                private void release() {
                  request.done();
                  _jobs.remove(request.getId());
                  _moverByRequests.remove(request.getDoorUniqueId());
                }

                @Override
                public void failed(Throwable exc, Void attachment) {
                  release();
                }
              });
    }
  }
  private void sendToExecution(final PrioritizedRequest request) {
    try (CDC ignore = request.getCdc().restore()) {
      request.transfer(
          new CompletionHandler<Void, Void>() {
            @Override
            public void completed(Void result, Void attachment) {
              postprocess();
            }

            @Override
            public void failed(Throwable exc, Void attachment) {
              if (exc instanceof InterruptedException || exc instanceof InterruptedIOException) {
                request
                    .getMover()
                    .setTransferStatus(CacheException.DEFAULT_ERROR_CODE, "Transfer was killed");
              } else if (exc instanceof DiskErrorCacheException) {
                FaultEvent faultEvent =
                    new FaultEvent("transfer", FaultAction.DISABLED, exc.getMessage(), exc);
                _faultListeners.forEach(l -> l.faultOccurred(faultEvent));
              }
              postprocess();
            }

            private void postprocess() {
              try (CDC ignore = request.getCdc().restore()) {
                request
                    .getMover()
                    .close(
                        new CompletionHandler<Void, Void>() {
                          @Override
                          public void completed(Void result, Void attachment) {
                            release();
                          }

                          @Override
                          public void failed(Throwable exc, Void attachment) {
                            if (exc instanceof DiskErrorCacheException) {
                              FaultEvent faultEvent =
                                  new FaultEvent(
                                      "post-processing",
                                      FaultAction.DISABLED,
                                      exc.getMessage(),
                                      exc);
                              _faultListeners.forEach(l -> l.faultOccurred(faultEvent));
                            }
                            release();
                          }

                          private void release() {
                            request.done();
                            _jobs.remove(request.getId());
                            _moverByRequests.remove(request.getDoorUniqueId());
                            PrioritizedRequest nextRequest = nextOrRelease();
                            if (nextRequest != null) {
                              sendToExecution(nextRequest);
                            }
                          }
                        });
              }
            }
          });
    }
  }