@Override
  public Future<Void> updateBlobTtl(RestRequest restRequest, String blobId, String serviceId, long expiresAtMs,
      Callback<Void> callback, QuotaChargeCallback quotaChargeCallback) {
    //TODO: once the id converter logic moved in for router.updateTTL, we need to pass in internal header with blob id,
    //and use it to gate if we want to convert to get the blob id before the actual blob updated ttl.
    if (blobId == null) {
      throw new IllegalArgumentException("blobId must not be null");
    }
    currentOperationsCount.incrementAndGet();
    routerMetrics.updateBlobTtlOperationRate.mark();
    routerMetrics.operationQueuingRate.mark();
    FutureResult<Void> futureResult = new FutureResult<>();
    Callback<String> stringCallback = (result, exception) -> {
      // Create a new Callback<Void> and call it, ignoring the String result.
      callback.onCompletion(null, exception);
    };
    Callback<Void> wrappedCallback =
        restRequest != null ? createIdConverterCallbackForTtlUpdateAndDelete(restRequest, blobId, futureResult,
            stringCallback) : callback;
    if (isOpen.get()) {
      if (notFoundCache.getIfPresent(blobId) != null) {
        // If we know that blob doesn't exist, complete the operation.
        logger.info("Blob {} is known to be missing in servers", blobId);
        RouterException routerException =
            new RouterException("TtlUpdateOperation failed because of BlobNotFound", RouterErrorCode.BlobDoesNotExist);
        completeOperation(futureResult, wrappedCallback, null, routerException);
      } else {
        getOperationController().updateBlobTtl(blobId, serviceId, expiresAtMs, futureResult,
            new BlobOperationCallbackWrapper<>(blobId, wrappedCallback), quotaChargeCallback);
      }
    } else {
      RouterException routerException =
          new RouterException("Cannot accept operation because Router is closed", RouterErrorCode.RouterClosed);
      completeUpdateBlobTtlOperation(routerException, futureResult, wrappedCallback);
    }
    return futureResult;
  }
Requests that a blob's TTL be updated asynchronously and returns a future that will eventually contain information
about whether the request succeeded or not.

@param restRequest The {@link RestRequest} of updateBlobTtl
@param blobId      The ID of the blob that needs its TTL updated.
@param serviceId   The service ID of the service updating the blob. This can be null if unknown.
@param expiresAtMs The new expiry time (in ms) of the blob. Using {@link Utils#Infinite_Time} makes the blob
                   permanent
@param callback    The {@link Callback} which will be invoked on the completion of a request.
@return A future that would contain information about whether the update succeeded or not, eventually.
ambry-router/src/main/java/com/github/ambry/router/NonBlockingRouter.java