private int receive(
      Transaction[] txs, Session session, StatsManager statsManager, int lastTxIdx) {

    boolean isSessionReadable = true;
    int firstFailedIndex = Integer.MAX_VALUE;

    for (int j = 0; j <= lastTxIdx && isSessionReadable; j++) {
      switch (txs[j].getState()) {
        case PROCESSED:
          continue;
        case FATAL_ERROR:
          firstFailedIndex = Math.min(j, firstFailedIndex);
          continue;
        default:
      }

      Response response = txs[j].getResponse();

      try {
        session.read(response);
        txs[j].setState(TransactionState.PROCESSED);
        statsManager.recordResponseTime(
            txs[j].getCommand().getCommandType(), txs[j].getResponseTime());
      } catch (ParsingException pe) {
        txs[j].setState(TransactionState.FATAL_ERROR);
        if (pe.getCause() instanceof SAXException) {
          SAXException saxe = (SAXException) pe.getCause();
          userLogger.warning(saxe.getMessage());
          txs[j].setCause(pe.getCause());
        } else {
          userLogger.warning(pe.getMessage());
          txs[j].setCause(pe);
        }

        // We can't trust that the message boundaries will be correct
        // following a parsing error, so the session must be closed in
        // order to prevent incorrect interpretation of further service
        // elements received via this session.
        session.close();
        firstFailedIndex = Math.min(j, firstFailedIndex);
        isSessionReadable = false;
      } catch (IOException ioe) {
        userLogger.warning(ioe.getMessage());
        txs[j].setState(TransactionState.RETRY);
        txs[j].setCause(ioe);
        firstFailedIndex = Math.min(j, firstFailedIndex);
        isSessionReadable = false;
      }
    }

    return Math.min(firstFailedIndex, txs.length);
  }
  /** Return the index of the last transaction considered for sending. */
  private int send(Transaction[] txs, Session session, StatsManager statsManager)
      throws IOException {

    for (int i = 1; i < txs.length; i++) {
      switch (txs[i].getState()) {
        case PROCESSED:
        case FATAL_ERROR:
          continue;
        default:
      }

      Command command = txs[i].getCommand();
      txs[i].start();

      try {
        session.write(command);
        statsManager.incCommandCounter(command.getCommandType());
      } catch (ParsingException pe) {
        txs[i].setState(TransactionState.FATAL_ERROR);
        if (pe.getCause() instanceof SAXException) {
          SAXException saxe = (SAXException) pe.getCause();
          userLogger.warning(saxe.getMessage());
          txs[i].setCause(saxe);
        } else {
          userLogger.warning(pe.getMessage());
          txs[i].setCause(pe);
        }
      } catch (IOException ioe) {
        userLogger.severe(ioe.getMessage());
        txs[i].setState(TransactionState.RETRY);
        txs[i].setCause(ioe);
        throw ioe;
      }
    }

    return txs.length - 1;
  }
  /**
   * Try to process a single transaction. Up to {@code MAX_ACCEPTABLE_FAIL_COUNT} attempts will be
   * made to process the transaction in cases where I/O errors or non-protocol server errors occur
   * during processing. Use of the underlying session is protected against concurrent use by other
   * threads by using the getSession/releaseSession features of this SessionManager's {@link
   * com.ausregistry.jtoolkit2.session.SessionPool}. This method guarantees that the session used
   * will be returned to the pool before the method returns.
   *
   * @throws FatalSessionException No session could be acquired to process the transaction. Check
   *     the exception message and log records for details.
   * @throws IOException Every attempt to execute the transaction command failed due to an
   *     IOException. This is the last such IOException.
   * @throws ParsingException Parsing of the response failed. Check the exception message for the
   *     cause.
   * @throws CommandFailedException The acceptable limit on the number of failed commands due to
   *     server error was exceeded in trying to process the command. This probably indicates a
   *     server limitation related to the command being processed.
   * @throws IllegalStateException The SessionManager had been shutdown or not started up prior to
   *     invoking this method.
   */
  @Override
  public void execute(Transaction tx)
      throws FatalSessionException, IOException, ParsingException, CommandFailedException,
          IllegalStateException {

    debugLogger.finest("enter");

    if (state == SMState.STOPPED) {
      throw new IllegalStateException();
    }

    Command cmd = tx.getCommand();
    Response response = tx.getResponse();

    int failCount = 0;
    boolean isExecuted = false;
    Session session = null;

    // if only processing one transaction, get a new session for each
    // attempt in case the session fails mid-transaction.
    while (!isExecuted && state != SMState.STOPPED) {
      try {
        session = sessionPool.getSession(cmd.getCommandType());
        StatsManager statsManager = session.getStatsManager();
        statsManager.incCommandCounter(cmd.getCommandType());

        tx.start();
        session.write(cmd);
        isExecuted = true;
        session.read(response);
        tx.setState(TransactionState.PROCESSED);
        statsManager.recordResponseTime(cmd.getCommandType(), tx.getResponseTime());

        Result[] results = response.getResults();
        assert results != null;
        if (results != null) {
          for (Result result : results) {
            assert result != null;
            statsManager.incResultCounter(result.getResultCode());
            int code = result.getResultCode();

            switch (code) {
              case ResultCode.CMD_FAILED:
                throw new CommandFailedException();
              case ResultCode.CMD_FAILED_CLOSING:
              default:
                throw new CommandFailedException();
            }
          }
        }
      } catch (CommandFailedException cfe) {
        userLogger.warning(cfe.getMessage());

        if (state != SMState.STOPPED && failCount < MAX_ACCEPTABLE_FAIL_COUNT) {

          failCount++;
        } else {
          throw cfe;
        }
      } catch (IOException ioe) {
        userLogger.warning(ioe.getMessage());
        userLogger.warning("net.socket.closed");
        session.close();
        if (state != SMState.STOPPED && failCount < MAX_ACCEPTABLE_FAIL_COUNT) {
          failCount++;
        } else {
          throw ioe;
        }
      } catch (InterruptedException ie) {
        // if interrupted by shutdown, then started will be false
        // Note: this still enters the finally block
        continue;
      } catch (SessionConfigurationException sce) {
        throw new FatalSessionException(sce);
      } catch (SessionOpenException soe) {
        throw new FatalSessionException(soe);
      } finally {
        sessionPool.releaseSession(session);
      }
    }

    if (!isExecuted && state == SMState.STOPPED) {
      throw new IllegalStateException();
    }

    debugLogger.finest("exit");
  }