private <A> A getNoLock(long recid, Serializer<A> serializer) {
      if (fullTx) {
        Fun.Tuple2 tu = mod.get(recid);
        if (tu != null) {
          if (tu.a == TOMBSTONE) return null;
          return (A) tu.a;
        }
      }

      Object oldVal = old.get(recid);
      if (oldVal != null) {
        if (oldVal == TOMBSTONE) return null;
        return (A) oldVal;
      }
      return TxEngine.this.get(recid, serializer);
    }
 @Override
 public <A> void update(long recid, A value, Serializer<A> serializer) {
   if (!fullTx) throw new UnsupportedOperationException("read-only");
   commitLock.readLock().lock();
   try {
     mod.put(recid, Fun.t2(value, serializer));
   } finally {
     commitLock.readLock().unlock();
   }
 }
 @Override
 public <A> long put(A value, Serializer<A> serializer) {
   if (!fullTx) throw new UnsupportedOperationException("read-only");
   commitLock.writeLock().lock();
   try {
     Long recid = preallocRecidTake();
     usedPreallocatedRecids.add(recid);
     mod.put(recid, Fun.t2(value, serializer));
     return recid;
   } finally {
     commitLock.writeLock().unlock();
   }
 }
    @Override
    public <A> boolean compareAndSwap(
        long recid, A expectedOldValue, A newValue, Serializer<A> serializer) {
      if (!fullTx) throw new UnsupportedOperationException("read-only");

      commitLock.readLock().lock();
      try {

        Lock lock = locks[Store.lockPos(recid)].writeLock();
        lock.lock();
        try {
          A oldVal = getNoLock(recid, serializer);
          boolean ret = oldVal != null && oldVal.equals(expectedOldValue);
          if (ret) {
            mod.put(recid, Fun.t2(newValue, serializer));
          }
          return ret;
        } finally {
          lock.unlock();
        }
      } finally {
        commitLock.readLock().unlock();
      }
    }
    @Override
    public void commit() {
      if (!fullTx) throw new UnsupportedOperationException("read-only");

      commitLock.writeLock().lock();
      try {
        if (closed) return;
        if (uncommitedData) throw new IllegalAccessError("uncomitted data");
        txs.remove(ref);
        cleanTxQueue();

        if (pojo.hasUnsavedChanges()) pojo.save(this);

        // check no other TX has modified our data
        LongMap.LongMapIterator oldIter = old.longMapIterator();
        while (oldIter.moveToNext()) {
          long recid = oldIter.key();
          for (Reference<Tx> ref2 : txs) {
            Tx tx = ref2.get();
            if (tx == this || tx == null) continue;
            if (tx.mod.containsKey(recid)) {
              close();
              throw new TxRollbackException();
            }
          }
        }

        LongMap.LongMapIterator<Fun.Tuple2> iter = mod.longMapIterator();
        while (iter.moveToNext()) {
          long recid = iter.key();
          if (old.containsKey(recid)) {
            close();
            throw new TxRollbackException();
          }
        }

        iter = mod.longMapIterator();
        while (iter.moveToNext()) {
          long recid = iter.key();

          Fun.Tuple2 val = iter.value();
          Serializer ser = (Serializer) val.b;
          Object old = superGet(recid, ser);
          if (old == null) old = TOMBSTONE;
          for (Reference<Tx> txr : txs) {
            Tx tx = txr.get();
            if (tx == null || tx == this) continue;
            tx.old.putIfAbsent(recid, old);
          }

          if (val.a == TOMBSTONE) {
            superDelete(recid, ser);
          } else {
            superUpdate(recid, val.a, ser);
          }
        }

        // there are no conflicts, so update the POJO in parent
        // TODO sort of hack, is it thread safe?
        getWrappedEngine().getSerializerPojo().registered = pojo.registered;
        superCommit();

        close();
      } finally {
        commitLock.writeLock().unlock();
      }
    }
 @Override
 public void close() {
   closed = true;
   old.clear();
   ref.clear();
 }