@Override
  protected void decode(ChannelHandlerContext ctx, RESPONSE msg, List<Object> out)
      throws Exception {
    if (currentDecodingState == DecodingState.INITIAL) {
      currentRequest = sentRequestQueue.poll();
      currentDecodingState = DecodingState.STARTED;
      if (currentRequest != null) {
        Long st = sentRequestTimings.poll();
        if (st != null) {
          currentOpTime = System.nanoTime() - st;
        } else {
          currentOpTime = -1;
        }
      }

      if (traceEnabled) {
        LOGGER.trace("{}Started decoding of {}", logIdent(ctx, endpoint), currentRequest);
      }
    }

    try {
      CouchbaseResponse response = decodeResponse(ctx, msg);
      if (response != null) {
        publishResponse(response, currentRequest.observable());

        if (currentDecodingState == DecodingState.FINISHED) {
          if (currentRequest != null
              && currentOpTime >= 0
              && env() != null
              && env().networkLatencyMetricsCollector().isEnabled()) {
            NetworkLatencyMetricsIdentifier identifier =
                new NetworkLatencyMetricsIdentifier(
                    remoteHostname,
                    serviceType().toString(),
                    currentRequest.getClass().getSimpleName(),
                    response.status().toString());
            env().networkLatencyMetricsCollector().record(identifier, currentOpTime);
          }
        }
      }
    } catch (CouchbaseException e) {
      currentRequest.observable().onError(e);
    } catch (Exception e) {
      currentRequest.observable().onError(new CouchbaseException(e));
    }

    if (currentDecodingState == DecodingState.FINISHED) {
      if (traceEnabled) {
        LOGGER.trace("{}Finished decoding of {}", logIdent(ctx, endpoint), currentRequest);
      }
      currentRequest = null;
      currentDecodingState = DecodingState.INITIAL;
    }
  }
 /**
  * Override to customize the behavior when a keep alive has been responded to.
  *
  * <p>The default behavior is to log the event and the response status at trace level.
  *
  * @param ctx the channel context.
  * @param keepAliveResponse the keep alive request that was sent when keep alive was triggered
  */
 protected void onKeepAliveResponse(
     ChannelHandlerContext ctx, CouchbaseResponse keepAliveResponse) {
   if (traceEnabled) {
     LOGGER.trace(
         logIdent(ctx, endpoint) + "keepAlive was answered, status " + keepAliveResponse.status());
   }
 }
  /**
   * Cancells any outstanding operations which are currently on the wire.
   *
   * @param ctx the handler context.
   */
  private void handleOutstandingOperations(final ChannelHandlerContext ctx) {
    if (sentRequestQueue.isEmpty()) {
      LOGGER.trace(logIdent(ctx, endpoint) + "Not cancelling operations - sent queue is empty.");
      return;
    }

    LOGGER.debug(
        logIdent(ctx, endpoint)
            + "Cancelling "
            + sentRequestQueue.size()
            + " outstanding requests.");
    while (!sentRequestQueue.isEmpty()) {
      REQUEST req = sentRequestQueue.poll();
      try {
        sideEffectRequestToCancel(req);
        req.observable().onError(new RequestCancelledException("Request cancelled in-flight."));
      } catch (Exception ex) {
        LOGGER.info("Exception thrown while cancelling outstanding operation: " + req, ex);
      }
    }

    sentRequestTimings.clear();
  }