@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());
   }
 }
 /**
  * Publishes a response with the attached observable.
  *
  * @param response the response to publish.
  * @param observable pushing into the event sink.
  */
 protected void publishResponse(
     final CouchbaseResponse response,
     final Subject<CouchbaseResponse, CouchbaseResponse> observable) {
   if (response.status() != ResponseStatus.RETRY && observable != null) {
     final Scheduler.Worker worker = env().scheduler().createWorker();
     worker.schedule(
         new Action0() {
           @Override
           public void call() {
             try {
               observable.onNext(response);
               observable.onCompleted();
             } catch (Exception ex) {
               LOGGER.warn("Caught exception while onNext on observable", ex);
               observable.onError(ex);
             } finally {
               worker.unsubscribe();
             }
           }
         });
   } else {
     responseBuffer.publishEvent(ResponseHandler.RESPONSE_TRANSLATOR, response, observable);
   }
 }