/**
   * Sends an RPC request to the given {@link InetSocketAddress}.
   *
   * @param remoteSocketAddress the remote address to send the request to
   * @param request the RPC request to send
   * @return the return value of the RPC call, possibly <code>null</code>
   * @throws Throwable any exception that is thrown by the remote receiver of the RPC call
   */
  Object sendRPCRequest(final InetSocketAddress remoteSocketAddress, final RPCRequest request)
      throws Throwable {

    if (this.shutdownRequested.get())
      throw new IOException("Shutdown of RPC service has already been requested");

    final long start = System.currentTimeMillis();
    final DatagramPacket[] packets = this.messageToPackets(remoteSocketAddress, request);
    final Integer messageID = Integer.valueOf(request.getMessageID());

    final RPCRequestMonitor requestMonitor = new RPCRequestMonitor();

    this.pendingRequests.put(messageID, requestMonitor);

    RPCResponse rpcResponse = null;
    int numberOfRetries;
    try {

      numberOfRetries = this.networkThread.send(packets);

      // Wait for the response
      synchronized (requestMonitor) {
        while (true) {

          if (requestMonitor.rpcResponse != null) {
            rpcResponse = requestMonitor.rpcResponse;
            break;
          }

          final long sleepTime = RPC_TIMEOUT - (System.currentTimeMillis() - start);
          if (sleepTime > 0L) requestMonitor.wait(sleepTime);
          else break;
        }
      }

    } finally {
      // Request is no longer pending
      this.pendingRequests.remove(messageID);
    }

    if (rpcResponse == null)
      throw new IOException(
          "Unable to complete RPC of method "
              + request.getMethodName()
              + " on "
              + remoteSocketAddress);

    // Report the successful call to the statistics module
    final String methodName = request.getMethodName();
    this.statistics.reportSuccessfulTransmission(methodName, packets.length, numberOfRetries);
    this.statistics.reportRTT(methodName, (int) (System.currentTimeMillis() - start));

    // TODO: Send clean up message

    if (rpcResponse instanceof RPCReturnValue) return ((RPCReturnValue) rpcResponse).getRetVal();
    throw ((RPCThrowable) rpcResponse).getThrowable();
  }
  /**
   * Processes an incoming RPC response.
   *
   * @param rpcResponse the RPC response to be processed
   */
  void processIncomingRPCResponse(final RPCResponse rpcResponse) {

    final Integer messageID = Integer.valueOf(rpcResponse.getMessageID());

    final RPCRequestMonitor requestMonitor = this.pendingRequests.get(messageID);

    // The caller has already timed out or received an earlier response
    if (requestMonitor == null) return;

    synchronized (requestMonitor) {
      requestMonitor.rpcResponse = rpcResponse;
      requestMonitor.notify();
    }
  }