void handleResponse(ResponseInfo responseInfo, GetResponse getResponse) {
      int correlationId = responseInfo.getRequestInfo().getRequest().getCorrelationId();
      // Get the GetOperation that generated the request.
      RequestInfo getRequestInfo = correlationIdToGetRequestInfo.remove(correlationId);
      if (getRequestInfo == null) {
        // Ignore right away. This associated operation has completed.
        return;
      }
      if (responseInfo.isQuotaRejected()) {
        processQuotaRejectedResponse(correlationId, getRequestInfo.getReplicaId());
        return;
      }
      // Track the over all time taken for the response since the creation of the request.
      long requestLatencyMs = time.milliseconds() - getRequestInfo.getRequestCreateTime();
      routerMetrics.routerRequestLatencyMs.update(requestLatencyMs);
      routerMetrics.getDataNodeBasedMetrics(getRequestInfo.getReplicaId().getDataNodeId()).getRequestLatencyMs.update(
          requestLatencyMs);
      if (responseInfo.getError() != null) {
        // responseInfo.getError() returns NetworkClientErrorCode. If error is not null, it probably means (1) connection
        // checkout timed out; (2) pending connection timed out; (3) established connection timed out. In all these cases,
        // the latency histogram in adaptive operation tracker should not be updated.
        logger.trace("GetBlobRequest with response correlationId {} timed out for replica {} ", correlationId,
            getRequestInfo.getReplicaId().getDataNodeId());
        onErrorResponse(getRequestInfo.getReplicaId(), buildChunkException(
            "Operation timed out because of " + responseInfo.getError() + " at DataNode " + responseInfo.getDataNode(),
            RouterErrorCode.OperationTimedOut));
      } else {
        if (getResponse == null) {
          logger.trace(
              "GetBlobRequest with response correlationId {} received an unexpected error on response deserialization from replica {} ",
              correlationId, getRequestInfo.getReplicaId().getDataNodeId());
          onErrorResponse(getRequestInfo.getReplicaId(),
              buildChunkException("Response deserialization received an unexpected error",
                  RouterErrorCode.UnexpectedInternalError));
        } else {
          if (getResponse.getCorrelationId() != correlationId) {
            // The NetworkClient associates a response with a request based on the fact that only one request is sent
            // out over a connection id, and the response received on a connection id must be for the latest request
            // sent over it. The check here ensures that is indeed the case. If not, log an error and fail this request.
            // There is no other way to handle it.
            logger.trace("GetBlobRequest with response correlationId {} mismatch from response {} for replica {} ",
                correlationId, getResponse.getCorrelationId(), getRequestInfo.getReplicaId().getDataNodeId());
            routerMetrics.unknownReplicaResponseError.inc();
            onErrorResponse(getRequestInfo.getReplicaId(), buildChunkException(
                "The correlation id in the GetResponse " + getResponse.getCorrelationId()
                    + " is not the same as the correlation id in the associated GetRequest: " + correlationId,
                RouterErrorCode.UnexpectedInternalError));
            // we do not notify the ResponseHandler responsible for failure detection as this is an unexpected error.
          } else {
            try {
              processGetBlobResponse(getRequestInfo, getResponse);
            } catch (IOException | MessageFormatException e) {
              // This should really not happen. Again, we do not notify the ResponseHandler responsible for failure
              // detection.
              logger.trace(
                  "GetBlobRequest with response correlationId {} response deserialization failed for replica {} ",
                  correlationId, getRequestInfo.getReplicaId().getDataNodeId());
              routerMetrics.responseDeserializationErrorCount.inc();
              onErrorResponse(getRequestInfo.getReplicaId(),
                  buildChunkException("Response deserialization received an unexpected error", e,
                      RouterErrorCode.UnexpectedInternalError));
            }
          }
        }
      }
      checkAndMaybeComplete();
    }
This method is the entry point for handling responses received for requests sent out on behalf of this chunk.
For gets, processing involves determining whether the request was successful, notifying the operation
tracker so it can track the status of the operation, and notifying the response handler for failure detection.
Finally, a check is done to determine whether the operation on the chunk is eligible for completion,
if so the chunk operation is completed right away.
@param responseInfo the response received for a request sent out on behalf of this chunk.
@param getResponse the {@link GetResponse} associated with this response.
ambry-router/src/main/java/com/github/ambry/router/GetBlobOperation.java