/**
  * Constructs a SyncProposalProcessor object.
  *
  * @param persistence the persistent variables.
  * @param transport used to send acknowledgment.
  * @param maxBatchSize the maximum batch size.
  * @throws IOException in case of IO failure.
  */
 public SyncProposalProcessor(PersistentState persistence, Transport transport, int maxBatchSize)
     throws IOException {
   this.persistence = persistence;
   this.log = persistence.getLog();
   this.transport = transport;
   this.maxBatchSize = maxBatchSize;
   this.serverId = persistence.getLastSeenConfig().getServerId();
   ExecutorService es = Executors.newSingleThreadExecutor(DaemonThreadFactory.FACTORY);
   ft = es.submit(this);
   es.shutdown();
 }
  @Override
  public Void call() throws Exception {
    try {
      LOG.debug("Batched SyncRequestProcessor gets started.");
      MessageTuple lastReq = null;
      // Number of transactions batched so far.
      int batchCount = 0;

      while (true) {
        MessageTuple req;
        if (lastReq == null) {
          req = this.proposalQueue.take();
        } else {
          req = this.proposalQueue.poll();
          if (req == null || batchCount == maxBatchSize || req == MessageTuple.REQUEST_OF_DEATH) {
            // Sync to disk and send ACK to leader.
            this.log.sync();
            Zxid zxid = MessageBuilder.fromProtoZxid(lastReq.getMessage().getProposal().getZxid());
            sendAck(lastReq.getServerId(), zxid);
            batchCount = 0;
          }
        }
        if (req == MessageTuple.REQUEST_OF_DEATH) {
          break;
        }
        if (req == null) {
          lastReq = null;
          continue;
        }
        if (req.getMessage().getType() == MessageType.PROPOSAL) {
          // It's PROPOSAL, sync to disk.
          Message msg = req.getMessage();
          Transaction txn = MessageBuilder.fromProposal(msg.getProposal());
          // TODO : avoid this?
          ByteBuffer body = txn.getBody().asReadOnlyBuffer();
          LOG.debug("Syncing transaction {} to disk.", txn.getZxid());
          this.log.append(txn);
          batchCount++;
          lastReq = req;
          if (txn.getType() == ProposalType.COP_VALUE) {
            // If it's COP, we should also update cluster_config file.
            ClusterConfiguration cnf = ClusterConfiguration.fromByteBuffer(body, this.serverId);
            persistence.setLastSeenConfig(cnf);
            // If it's COP, we shouldn't batch it, send ACK immediatly.
            sendAck(req.getServerId(), txn.getZxid());
            batchCount = 0;
            lastReq = null;
          }
        }
      }
    } catch (Exception e) {
      LOG.error("Caught an exception in SyncProposalProcessor", e);
      throw e;
    }
    LOG.debug("SyncProposalProcessor has been shut down.");
    return null;
  }