void endTransactionTPL(Session session) {

    int unlockedCount = 0;

    unlockTablesTPL(session);

    final int waitingCount = session.waitingSessions.size();

    if (waitingCount == 0) {
      return;
    }

    for (int i = 0; i < waitingCount; i++) {
      Session current = (Session) session.waitingSessions.get(i);

      current.tempUnlocked = false;

      long count = current.latch.getCount();

      if (count == 1) {
        boolean canProceed = setWaitedSessionsTPL(current, current.currentStatement);

        if (!canProceed) {
          current.abortTransaction = true;
        }

        if (current.tempSet.isEmpty()) {
          lockTablesTPL(current, current.currentStatement);

          current.tempUnlocked = true;

          unlockedCount++;
        }
      }
    }

    for (int i = 0; i < waitingCount; i++) {
      Session current = (Session) session.waitingSessions.get(i);

      if (!current.tempUnlocked) {

        // this can introduce additional waits for the sessions
        boolean canProceed = setWaitedSessionsTPL(current, current.currentStatement);

        if (!canProceed) {
          current.abortTransaction = true;
        }
      }
    }

    for (int i = 0; i < waitingCount; i++) {
      Session current = (Session) session.waitingSessions.get(i);

      setWaitingSessionTPL(current);
    }

    session.tempSet.clear();
    session.waitingSessions.clear();
  }
  /** add session to the end of queue when a transaction starts (depending on isolation mode) */
  public void beginAction(Session session, Statement cs) {

    if (session.hasLocks(cs)) {
      return;
    }

    writeLock.lock();

    try {
      if (cs.getCompileTimestamp() < database.schemaManager.getSchemaChangeTimestamp()) {
        cs = session.statementManager.getStatement(session, cs);
        session.sessionContext.currentStatement = cs;

        if (cs == null) {
          return;
        }
      }

      boolean canProceed = setWaitedSessionsTPL(session, cs);

      if (canProceed) {
        if (session.tempSet.isEmpty()) {
          lockTablesTPL(session, cs);

          // we don't set other sessions that would now be waiting for this one too
        } else {
          setWaitingSessionTPL(session);
        }
      } else {
        session.abortTransaction = true;
      }
    } finally {
      writeLock.unlock();
    }
  }
  /** add session to the end of queue when a transaction starts (depending on isolation mode) */
  public void beginAction(Session session, Statement cs) {

    synchronized (liveTransactionTimestamps) {
      session.actionTimestamp = nextChangeTimestamp();

      if (!session.isTransaction) {
        session.transactionTimestamp = session.actionTimestamp;
        session.isTransaction = true;

        liveTransactionTimestamps.addLast(session.actionTimestamp);
      }
    }

    if (session.hasLocks()) {
      return;
    }

    try {
      writeLock.lock();

      boolean canProceed = beginActionTPL(session, cs);

      if (!canProceed) {
        session.abortTransaction = true;
      }
    } finally {
      writeLock.unlock();
    }
  }
  public void rollback(Session session) {

    session.abortTransaction = false;
    session.actionTimestamp = nextChangeTimestamp();

    rollbackPartial(session, 0, session.transactionTimestamp);
    endTransaction(session);

    try {
      writeLock.lock();
      endTransactionTPL(session);
    } finally {
      writeLock.unlock();
    }
  }
  public boolean prepareCommitActions(Session session) {

    Object[] list = session.rowActionList.getArray();
    int limit = session.rowActionList.size();

    if (session.abortTransaction) {

      //            System.out.println("cascade fail " + session + " " + session.actionTimestamp);
      return false;
    }

    try {
      writeLock.lock();

      for (int i = 0; i < limit; i++) {
        RowAction rowact = (RowAction) list[i];

        if (!rowact.canCommit(session, session.tempSet)) {

          //                System.out.println("commit conflicts " + session + " " +
          // session.actionTimestamp);
          return false;
        }
      }

      session.actionTimestamp = nextChangeTimestamp();

      for (int i = 0; i < limit; i++) {
        RowAction action = (RowAction) list[i];

        action.prepareCommit(session);
      }

      for (int i = 0; i < session.tempSet.size(); i++) {
        Session current = (Session) session.tempSet.get(i);

        current.abortTransaction = true;
      }

      return true;
    } finally {
      writeLock.unlock();
      session.tempSet.clear();
    }
  }
  public void completeActions(Session session) {

    Object[] list = session.rowActionList.getArray();
    int limit = session.rowActionList.size();
    boolean canComplete = true;

    writeLock.lock();

    try {
      for (int i = session.actionIndex; i < limit; i++) {
        RowAction rowact = (RowAction) list[i];

        if (rowact.complete(session, session.tempSet)) {
          continue;
        }

        canComplete = false;

        if (session.isolationMode == SessionInterface.TX_REPEATABLE_READ
            || session.isolationMode == SessionInterface.TX_SERIALIZABLE) {
          session.abortTransaction = true;

          break;
        }
      }

      for (int i = session.actionIndex; canComplete && i < limit; i++) {
        RowAction action = (RowAction) list[i];

        if (!action.table.isLogged) {
          continue;
        }

        Row row = action.memoryRow;

        if (row == null) {
          PersistentStore store = session.sessionData.getRowStore(action.table);

          row = (Row) store.get(action.getPos(), false);
        }

        Object[] data = row.getData();

        try {
          int actionType = action.getActionType(session.actionTimestamp);

          if (actionType == RowActionBase.ACTION_INSERT) {
            database.logger.writeInsertStatement(session, (Table) action.table, data);
          } else if (actionType == RowActionBase.ACTION_DELETE) {
            database.logger.writeDeleteStatement(session, (Table) action.table, data);
          } else if (actionType == RowActionBase.ACTION_NONE) {

            // no logging
          } else {
            throw Error.runtimeError(ErrorCode.U_S0500, "TransactionManager");
          }
        } catch (HsqlException e) {

          // can put db in special state
        }
      }

      if (!canComplete && !session.abortTransaction) {
        session.redoAction = true;

        rollbackAction(session);

        if (!session.tempSet.isEmpty()) {
          session.latch.setCount(session.tempSet.size());

          for (int i = 0; i < session.tempSet.size(); i++) {
            Session current = (Session) session.tempSet.get(i);

            current.waitingSessions.add(session);

            // waitedSessions.put(current, session);
            // waitingSessions.put(session, current);
          }
        }
      }
    } finally {
      writeLock.unlock();
      session.tempSet.clear();
    }
  }