/**
  * {@inheritDoc}
  *
  * <p>Release the underlying buffer of previous/current read response.
  */
 @Override
 public void close() throws IOException {
   if (mReadResponse != null) {
     mReadResponse.getPayloadDataBuffer().release();
     mReadResponse = null;
   }
 }
  @Override
  public ByteBuffer readRemoteBlock(
      InetSocketAddress address,
      long blockId,
      long offset,
      long length,
      long lockId,
      long sessionId)
      throws IOException {
    SingleResponseListener listener = null;
    Channel channel = null;
    Metrics.NETTY_BLOCK_READ_OPS.inc();
    try {
      channel = BlockStoreContext.acquireNettyChannel(address, mClientBootstrap);
      listener = new SingleResponseListener();
      channel.pipeline().get(ClientHandler.class).addListener(listener);
      ChannelFuture channelFuture =
          channel.writeAndFlush(
              new RPCBlockReadRequest(blockId, offset, length, lockId, sessionId));
      channelFuture = channelFuture.sync();
      if (channelFuture.isDone() && !channelFuture.isSuccess()) {
        LOG.error(
            "Failed to write to %s for block %d with error %s.",
            address.toString(), blockId, channelFuture.cause());
        throw new IOException(channelFuture.cause());
      }

      RPCResponse response = listener.get(NettyClient.TIMEOUT_MS, TimeUnit.MILLISECONDS);

      switch (response.getType()) {
        case RPC_BLOCK_READ_RESPONSE:
          RPCBlockReadResponse blockResponse = (RPCBlockReadResponse) response;
          LOG.debug("Data {} from remote machine {} received", blockId, address);

          RPCResponse.Status status = blockResponse.getStatus();
          if (status == RPCResponse.Status.SUCCESS) {
            // always clear the previous response before reading another one
            close();
            mReadResponse = blockResponse;
            return blockResponse.getPayloadDataBuffer().getReadOnlyByteBuffer();
          }
          throw new IOException(status.getMessage() + " response: " + blockResponse);
        case RPC_ERROR_RESPONSE:
          RPCErrorResponse error = (RPCErrorResponse) response;
          throw new IOException(error.getStatus().getMessage());
        default:
          throw new IOException(
              ExceptionMessage.UNEXPECTED_RPC_RESPONSE.getMessage(
                  response.getType(), RPCMessage.Type.RPC_BLOCK_READ_RESPONSE));
      }
    } catch (Exception e) {
      Metrics.NETTY_BLOCK_READ_FAILURES.inc();
      try {
        if (channel != null) {
          channel.close().sync();
        }
      } catch (InterruptedException ee) {
        throw Throwables.propagate(ee);
      }
      throw new IOException(e);
    } finally {
      if (channel != null && listener != null && channel.isActive()) {
        channel.pipeline().get(ClientHandler.class).removeListener(listener);
      }
      if (channel != null) {
        BlockStoreContext.releaseNettyChannel(address, channel);
      }
    }
  }