/* -*- indent-tabs-mode: nil -*- */
/*
  RSet.cpp

  Qore Programming Language

  Copyright (C) 2003 - 2017 Qore Technologies, s.r.o.

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the "Software"),
  to deal in the Software without restriction, including without limitation
  the rights to use, copy, modify, merge, publish, distribute, sublicense,
  and/or sell copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  DEALINGS IN THE SOFTWARE.

  Note that the Qore library is released under a choice of three open-source
  licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
  information.
*/

#include <qore/Qore.h>
#include "qore/intern/QoreObjectIntern.h"

RObject::~RObject() {
   assert(!rset);
}

bool RObject::scanCheck(RSetHelper& rsh, AbstractQoreNode* n) {
   return rsh.checkIntern(n);
}

void RObject::setRSet(RSet* rs, int rcnt) {
   assert(rml.checkRSectionExclusive());
   printd(QRO_LVL, "RObject::setRSet() this: %p %s rs: %p rcnt: %d\n", this, getName(), rs, rcnt);
   if (rset) {
      // invalidating the rset removes the weak references to all contained objects
      rset->invalidateDeref();
   }
   rset = rs;
   rcount = rcnt;
#ifdef DEBUG
   if (rcount > references)
      printd(0, "RObject::setRSet() this: %p '%s' cannot set rcount %d > references %d\n", this, getName(), rcount, references.load());
   assert(rcount <= references);
#endif
   if (rs) {
      rs->ref();
      // we make a weak reference from the rset to the object to ensure that it does not disappear while the rset is valid
      tRef();
   }
   // increment transaction count
   ++rcycle;
}

void RObject::derefRealIntern() {
   assert(rrefs > 0);
   // before allowing the real references to reach zero, we need to ensure that any rset invalidation action has completed
   while (rrefs == 1 && rref_wait) {
      ++rref_waiting;
      rcond.wait(rlck);
      --rref_waiting;
   }
   assert(rrefs > 0);

   --rrefs;
}

int RObject::deref(bool real, bool& do_scan, bool& rescan) {
   // the mutex ensures atomicity
   AutoLocker al(rlck);
   if (real)
      derefRealIntern();
   else
      assert(rrefs >= 0);

   // dereference the object and save the resulting value as the return value
   int rv_refs = --references;

   do_scan = !rrefs;

   if (do_scan) {
      rescan = deferred_scan;
      if (deferred_scan)
         deferred_scan = false;
   }
   else
      rescan = false;

   // mark that we have a dereference action in progress
   ++ref_inprogress;

   return rv_refs;
}

void RObject::derefDone(bool del) {
   AutoLocker al(rlck);
   // decrement the in progress count, if it's the last thread, and there are waiting threads, then wake one up
   if ((!--ref_inprogress) && ref_waiting) {
      // we have to use broadcast here because the condition variable is shared
      rcond.broadcast();
      assert(!del);
   }
   else if (del) {
      // if we are going to delete the object, then wait for all other in-progress calls to complete first
      while (ref_inprogress) {
         ++ref_waiting;
         rcond.wait(rlck);
         --ref_waiting;
      }
   }
}

int RObject::checkDeferScan() {
   {
      AutoLocker al(rlck);
      // if we have a "real reference" (a reference that cannot be recursive), then we delay the scan
      if (!rrefs) {
         // we are making a scan now, so if the deferred_scan flag is set, unset it
         if (deferred_scan)
            deferred_scan = false;
         printd(QRO_LVL, "RObject::checkDeferScan() this: %p (%s) rrefs: %d scan OK\n", this, getName(), rrefs);
         return 0;
      }
      if (deferred_scan) {
         printd(QRO_LVL, "RObject::checkDeferScan() this: %p (%s) rrefs: %d deferred_scan already set\n", this, getName(), rrefs);
         return -1;
      }
      printd(QRO_LVL, "RObject::checkDeferScan() this: %p (%s) rrefs: %d setting deferred_scan\n", this, getName(), rrefs);
      deferred_scan = true;
      // if there is no rset, we can return immediately, no rset can be attached while rrefs > 0
      if (!rset)
         return -1;
      // otherwise we need to invalidate the rset and ensure that
      // rrefs does not go to zero until this is done
      rref_wait = true;
   }

   removeInvalidateRSet();
   AutoLocker al(rlck);
   rref_wait = false;
   if (rref_waiting)
      rcond.broadcast();

   return -1;
}

void RObject::removeInvalidateRSet() {
   QoreAutoVarRWWriteLocker al(rml);
   removeInvalidateRSetIntern();
}

void RObject::removeInvalidateRSetIntern() {
   assert(rml.checkRSectionExclusive());
   if (rset) {
      // invalidating the rset removes the weak references to all contained objects
      rset->invalidateDeref();
      rset = 0;
      rcount = 0;
   }
}

#ifdef DEBUG
void RSet::dbg() {
   QoreAutoRWReadLocker al(rwl);

   printd(0, "RSet::dbg() this: %p valid: %d ssize: %d size: %d\n", this, (int)valid, (int)set.size(), (int)size());
   for (rset_t::iterator i = begin(), e = end(); i != e; ++i) {
      printd(0, " + %p '%s' rcount: %d refs: %d\n", *i, (*i)->getName(), (int)(*i)->rcount, (int)(*i)->refs());
   }
}
#endif

// if we return 1, the rset has been invalidated already
int RSet::canDelete(int ref_copy, int rcount) {
   printd(QRO_LVL, "RSet::canDelete() this: %p valid: %d\n", this, valid);

   if (q_disable_gc)
      return 0;

   if (!valid)
      return -1;

   {
      QoreAutoRWReadLocker al(rwl);
      if (!valid)
         return -1;

      // to avoid race conditions, we only delete in the thread where the references == rcount for the current object
      if (ref_copy != rcount)
         return 0;

      for (rset_t::iterator i = begin(), e = end(); i != e; ++i) {
         // we do not need to lock the object here; if there are no external references, then no external changes can be made
         if (!(*i)->isValid())
            return 0;
         if ((*i)->rcount != (*i)->refs()) {
            printd(QRO_LVL, "RSet::canDelete() this: %p cannot delete graph obj %p '%s' rcount: %d refs: %d\n", this, *i, (*i)->getName(), (*i)->rcount, (*i)->refs());
            return 0;
         }
         printd(QRO_LVL, "RSet::canDelete() this: %p can delete graph obj %p '%s' rcount: %d refs: %d\n", this, *i, (*i)->getName(), (*i)->rcount, (*i)->refs());
      }
   }

   // invalidate the rset
   QoreAutoRWWriteLocker al(rwl);
   if (!valid)
      return -1;

   invalidateIntern();

   printd(QRO_LVL, "RSet::canDelete() this: %p can delete all objects in graph\n", this);
   return 1;
}

robject_dereference_helper::robject_dereference_helper(RObject* obj, bool real) : o(obj), qo(0) {
   refs = obj->deref(real, do_scan, deferred_scan);
   del = !refs;
}

robject_dereference_helper::~robject_dereference_helper() {
   o->derefDone(del);

   if (del && qo)
      qo->tDeref();
}

class RSectionScanHelper {
protected:
   RSetHelper* orsh;
   RObject* ro;
   unsigned size;

public:
   DLLLOCAL RSectionScanHelper(RSetHelper* n_orsh, RObject* n_ro) : orsh(0), ro(n_ro) {
      int tid = gettid();

      // if we already have the rsection lock, then ignore; already processed (either in fomap or tr_out)
      if (n_ro->rml.hasRSectionLock(tid))
         return;

      // try to lock
      if (ro->rml.tryRSectionLockNotifyWaitRead(&n_orsh->notifier)) {
         ro = 0;
	 return;
      }

      orsh = n_orsh;
      size = n_orsh->size();
      orsh->inccnt();
   }

   DLLLOCAL ~RSectionScanHelper() {
      if (!orsh)
	 return;

      // if no objects were added to the set, then unlock the lock
      if (orsh->size() == size) {
	 ro->rml.rSectionUnlock();
         orsh->deccnt();
	 return;
      }

      // otherwise try to add the lock to the list to be released at the end of the scan
      orsh->add(ro);
   }

   DLLLOCAL bool lockError() const {
      return !ro;
   }
};

bool RSetHelper::checkIntern(AbstractQoreNode* n) {
   if (!needs_scan(n))
      return false;

   //printd(5, "RSetHelper::checkIntern() checking %p %s\n", n, get_type_name(n));

   switch (get_node_type(n)) {
      case NT_OBJECT:
         return checkIntern(*qore_object_private::get(*reinterpret_cast<QoreObject*>(n)));

      case NT_LIST: {
         QoreListNode* l = reinterpret_cast<QoreListNode*>(n);
         ListIterator li(l);
         while (li.next()) {
            if (checkIntern(li.getValue()))
               return true;
         }

         return false;
      }

      case NT_HASH: {
         QoreHashNode* h = reinterpret_cast<QoreHashNode*>(n);
         HashIterator hi(h);
         while (hi.next()) {
            assert(hi.getValue() != h);
            if (checkIntern(hi.getValue()))
               return true;
         }

         return false;
      }

      case NT_RUNTIME_CLOSURE: {
         QoreClosureBase* c = reinterpret_cast<QoreClosureBase*>(n);
         // do not lock or scan if the closure cannot contain any closure-bound local vars with objects or closures (also not through a container)
         if (!c->needsScan()) {
            printd(QRO_LVL, "RSetHelper::checkIntern() closure %p: no scan\n", c);
            return false;
         }
	 closure_set_t::iterator ci = closure_set.lower_bound(c);
	 // return false if already scanned
	 if (ci != closure_set.end() && *ci == c) {
            printd(QRO_LVL, "RSetHelper::checkIntern() found dup closure %p\n", c);
	    return false;
         }
	 // insert into scanned closure set
	 closure_set.insert(ci, c);

	 // do not scan any closure object since weak references are used
         // iterate captured lvars
         const cvar_map_t& cmap = c->getMap();

         for (cvar_map_t::const_iterator i = cmap.begin(), e = cmap.end(); i != e; ++i) {
            // do not grab the lock if the lvalue cannot contain an object or closure (also not through a container)
            if (!i->second->needsScan(true)) {
               printd(QRO_LVL, "RSetHelper::checkIntern() closure %p: var %s: no scan\n", c, i->first->getName());
               continue;
            }
	    RSectionScanHelper rssh(this, i->second);
            if (rssh.lockError())
               return true;
#ifdef DEBUG
	    unsigned csize = size();
#endif

            if (checkIntern(*i->second))
               return true;

#ifdef DEBUG
	    if (csize != size())
               printd(QRO_LVL, "RSetHelper::checkIntern() closure var '%s' found rref (type: %s)\n", i->first->getName(), i->second->val.getTypeName());
            else
               printd(QRO_LVL, "RSetHelper::checkIntern() closure var '%s' no rref; size: %d (type: %s)\n", i->first->getName(), csize, i->second->val.getTypeName());
#endif
         }

         return false;
      }

      case NT_REFERENCE:
         return lvalue_ref::get(static_cast<ReferenceNode*>(n))->scanReference(*this);

      case NT_VALUE_LIST:
         assert(false);
   }

   return false;
}

bool RSetHelper::removeInvalidate(RSet* ors, int tid) {
   // get a list of objects to be invalidated
   rvec_t rovec;

   {
      QoreAutoRWReadLocker al(ors->rwl);

      if (!ors->active())
         return false;

      // first grab all rsection locks
      for (rset_t::iterator ri = ors->begin(), re = ors->end(); ri != re; ++ri) {
         // if we already have the rsection lock, then ignore; already processed (either in fomap or tr_out)
         if ((*ri)->rml.hasRSectionLock(tid))
            continue;

         if ((*ri)->rml.tryRSectionLockNotifyWaitRead(&notifier)) {
            printd(QRO_LVL, "RSetHelper::removeInvalidate() obj %p '%s' cannot enter rsection: tid: %d\n", *ri, (*ri)->getName(), (*ri)->rml.rSectionTid());

            // release other rsection locks
            for (unsigned i = 0; i < rovec.size(); ++i) {
               rovec[i]->rml.rSectionUnlock();
               deccnt();
            }
            return true;
         }
#ifdef DEBUG
         // we always have the rsection lock here
         inccnt();
#endif

         // check object status; do not scan invalid objects or objects being deleted
         if (!(*ri)->isValid()) {
            (*ri)->rml.rSectionUnlock();
            deccnt();
            continue;
         }

         rovec.push_back(*ri);
      }
   }

   // invalidate old rset when transaction is committed
   tr_invalidate.insert(ors);

   // now process old rset
   for (unsigned i = 0; i < rovec.size(); ++i) {
      assert(rovec[i]->rml.hasRSectionLock());
      assert(rovec[i]->rset == ors);

      if (rovec[i]->isValid())
         tr_out.insert(rovec[i]);
   }

   return false;
}

bool RSetHelper::addToRSet(omap_t::iterator oi, RSet* rset, int tid) {
   // ensure that the current object is not in the rset
   assert(rset->find(oi->first) == rset->end());

   // queue mark object as finalized
   oi->second.finalize(rset);
   assert(oi->second.rset);

   // insert into new rset
   rset->insert(oi->first);

   // queue any nodes not scanned for rset invalidation
   if (oi->first->rset) {
      assert(rset != oi->first->rset);
      if (removeInvalidate(oi->first->rset, tid))
         return true;
   }

   printd(QRO_LVL, " + %p '%s': finalized with rset: %p (rcount: %d)\n", oi->first, oi->first->getName(), rset, oi->second.rcount);

   assert(!oi->second.in_cycle);
   oi->second.in_cycle = true;
   assert(oi->second.rset);
   // increment rcount
   printd(QRO_LVL, " + %p '%s': second.rset: %p final: %d ok: %d rcount: %d -> %d\n", oi->first, oi->first->getName(), oi->second.rset, oi->second.in_cycle, oi->second.ok, oi->second.rcount, oi->second.rcount + 1);
   ++oi->second.rcount;
   return false;
}

void RSetHelper::mergeRSet(int i, RSet*& rset) {
   omap_t::iterator oi = ovec[i];
   assert(oi->second.rset);

   printd(QRO_LVL, " + %p '%s': already finalized with rset: %p (size: %d, rcount: %d) current rset: %p (size: %d)\n", oi->first, oi->first->getName(), oi->second.rset, (int)oi->second.rset->size(), oi->second.rcount, rset ? rset : 0, rset ? (int)rset->size() : 0);

   if (rset == oi->second.rset) {
      printd(QRO_LVL, " + %p '%s': rset: %p (%d) already in set\n", oi->first, oi->first->getName(), rset, (int)rset->size());
   }
   else {
      // here we have to merge rsets
      if (!rset)
         rset = oi->second.rset;
      else if (rset->size() > oi->second.rset->size()) {
         printd(QRO_LVL, " + %p '%s': rset: %p (%d) assimilating %p (%d)\n", oi->first, oi->first->getName(), rset, (int)rset->size(), oi->second.rset, (int)oi->second.rset->size());
         // merge oi->second.rset into rset and retag objects
         RSet* old_rset = oi->second.rset;
         for (rset_t::iterator i = old_rset->begin(), e = old_rset->end(); i != e; ++i) {
            rset->insert(*i);
            omap_t::iterator noi = fomap.find(*i);
            assert(noi != fomap.end());
            noi->second.rset = rset;
         }
         delete old_rset;
      }
      else {
         printd(QRO_LVL, " + %p '%s': oi->second.rset: %p (%d) assimilating %p (%d)\n", oi->first, oi->first->getName(), oi->second.rset, (int)oi->second.rset->size(), rset, (int)rset->size());
         // merge rset into oi->second.rset and retag objects
         RSet* old_rset = rset;
         rset = oi->second.rset;
         for (rset_t::iterator i = old_rset->begin(), e = old_rset->end(); i != e; ++i) {
            rset->insert(*i);
            omap_t::iterator noi = fomap.find(*i);
            assert(noi != fomap.end());
            noi->second.rset = rset;
         }
         delete old_rset;
      }
   }
}

bool RSetHelper::makeChain(int i, omap_t::iterator fi, int tid) {
   for (++i; i < (int)ovec.size(); ++i) {
      if (!ovec[i]->second.rset) {
         printd(QRO_LVL, " + %p '%s': adding parent to rset\n", ovec[i]->first, ovec[i]->first->getName());
         if (addToRSet(ovec[i], fi->second.rset, tid))
            return true;
         if (i == (int)(ovec.size() - 1)) {
            printd(QRO_LVL, " + %p '%s': parent object %p '%s' was not in cycle (rcount: %d -> %d)\n", fi->first, fi->first->getName(), ovec[i]->first, ovec[i]->first->getName(), fi->second.rcount, fi->second.rcount + 1);
            ++fi->second.rcount;
            // rcount can never be more than real references for the target object
            assert(fi->first->references >= fi->second.rcount);
         }
      }
      else
         mergeRSet(i, fi->second.rset);
   }
   return false;
}

// XXX RSectionScanHelper
bool RSetHelper::checkIntern(RObject& obj) {
#ifdef DEBUG
   bool hl = obj.rml.hasRSectionLock();
#endif
   if (obj.rml.tryRSectionLockNotifyWaitRead(&notifier)) {
      printd(QRO_LVL, "RSetHelper::checkIntern() + obj %p '%s' cannot enter rsection: rsection tid: %d\n", &obj, obj.getName(), obj.rml.rSectionTid());
      return true;
   }
#ifdef DEBUG
   if (!hl)
      inccnt();
#endif

   // check object status; do not scan invalid objects or objects being deleted
   if (!obj.isValid()) {
      obj.rml.rSectionUnlock();
      deccnt();
      return false;
   }

   int tid = gettid();

   // see if the object has been scanned
   omap_t::iterator fi = fomap.lower_bound(&obj);
   if (fi != fomap.end() && fi->first == &obj) {
      printd(QRO_LVL, "RSetHelper::checkIntern() + found obj %p '%s' rcount: %d in_cycle: %d ok: %d\n", &obj, obj.getName(), fi->second.rcount, fi->second.in_cycle, fi->second.ok);
      //printd(QRO_LVL, "RSetHelper::checkIntern() found obj %p '%s' incrementing rcount: %d -> %d\n", &obj, obj.getName(), fi->second.rcount, fi->second.rcount + 1);

      if (fi->second.ok) {
         assert(!fi->second.in_cycle);
         printd(QRO_LVL, " + %p '%s' already scanned and ok\n", &obj, obj.getName());
         return false;
      }

      if (fi->second.in_cycle) {
         assert(fi->second.rset);
         // check if this object is part of the current cycle already - if
         // 1) it's already in the current scan vector, or
         // 2) the parent object of the current object is already a part of the recursive set
         if (inCurrentSet(fi)) {
            printd(QRO_LVL, " + recursive obj %p '%s' already finalized and in current cycle (rcount: %d -> %d)\n", &obj, obj.getName(), fi->second.rcount, fi->second.rcount + 1);
            ++fi->second.rcount;
            // rcount can never be more than real references for the target object
            assert(fi->first->references >= fi->second.rcount);
         }
         else if (!ovec.empty()) {
            // FIXME: use this optimization in the loop below
            if (ovec.back()->second.rset == fi->second.rset) {
               printd(QRO_LVL, " + %p '%s': parent object %p '%s' in same cycle (rcount: %d -> %d)\n", &obj, obj.getName(), ovec.back()->first, ovec.back()->first->getName(), fi->second.rcount, fi->second.rcount + 1);
               ++fi->second.rcount;
               // rcount can never be more than real references for the target object
               assert(fi->first->references >= fi->second.rcount);
               return false;
            }

            // see if any parent of the current object is already in the same recursive cycle, if so, we have a new chain (quick comparison first)
            for (int i = ovec.size() - 1; i >= 0; --i) {
               if (fi->second.rset == ovec[i]->second.rset) {
                  printd(QRO_LVL, " + recursive obj %p '%s' already finalized, cyclic ancestor %p '%s' in current cycle\n", &obj, obj.getName(), ovec[i]->first, ovec[i]->first->getName());

                  return makeChain(i, fi, tid);
               }
            }

            // see if any parent of the current object is already in a recursive cycle to be joined, if so, we have a new chain (slower comparison second)
            for (int i = ovec.size() - 1; i >= 0; --i) {
               if (fi->second.rset->find((ovec[i])->first) != fi->second.rset->end()) {
                  printd(QRO_LVL, " + recursive obj %p '%s' already finalized, cyclic ancestor %p '%s' in current cycle\n", &obj, obj.getName(), ovec[i]->first, ovec[i]->first->getName());

                  return makeChain(i, fi, tid);
               }
            }

            printd(QRO_LVL, " + recursive obj %p '%s' already finalized but not in current cycle\n", &obj, obj.getName());
            return false;
         }
      }
      else {
         if (!inCurrentSet(fi)) {
            printd(QRO_LVL, " + recursive obj %p '%s' not in current cycle\n", &obj, obj.getName());
            return false;
         }
      }

      // finalize current objects immediately
      RSet* rset = fi->second.rset;
#ifdef DEBUG
      if (rset)
         printd(QRO_LVL, " + %p '%s': reusing RSet: %p\n", &obj, obj.getName(), rset);
#endif

      int i = (int)ovec.size() - 1;
      while (i >= 0) {
         assert(i >= 0);

         // get iterator to object record
         omap_t::iterator oi = ovec[i];

         // merge rsets
         if (!oi->second.rset) {
            if (!rset) {
               rset = new RSet;
               printd(QRO_LVL, " + %p '%s': rcycle: %d second.rset: %p new RSet: %p\n", oi->first, oi->first->getName(), obj.rcycle, oi->second.rset, rset);
            }

            if (addToRSet(oi, rset, tid))
               return true;
         }
         else {
            if (i > 0 && oi->first != &obj && !ovec[i-1]->second.in_cycle) {
               printd(QRO_LVL, " + %p '%s': parent not yet in cycle (rcount: %d -> %d)\n", &obj, obj.getName(), oi->second.rcount, oi->second.rcount + 1);
               ++oi->second.rcount;
            }

            mergeRSet(i, rset);
         }

         if (oi->first == &obj)
            break;

         --i;
      }

      return false;
   }
   else {
      printd(QRO_LVL, "RSetHelper::checkIntern() + adding new obj %p '%s' setting rcount = 0 (current: %d rset: %p)\n", &obj, obj.getName(), obj.rcount, obj.rset);

      // insert into total scanned object set
      fi = fomap.insert(fi, omap_t::value_type(&obj, RSetStat()));

      // check if the object should be iterated
      if (!obj.needsScan(true)) {
         // remove from invalidation set if present
         tr_out.erase(&obj);

         printd(QRO_LVL, "RSetHelper::checkIntern() obj %p '%s' will not be iterated since object count is 0\n", &obj, obj.getName());
         fi->second.ok = true;
         assert(!fi->second.rset);
         return false;
      }
   }

   // push on current vector chain
   ovec.push_back(fi);

   // remove from invalidation set if present
   tr_out.erase(&obj);

   // recursively check data members
   if (obj.scanMembers(*this))
      return true;

   // remove from current vector chain
   ovec.pop_back();

   return false;
}

class RScanHelper {
public:
   RObject& obj;
   int rcycle;

   DLLLOCAL RScanHelper(RObject& o) : obj(o), rcycle(o.rcycle) {
      AutoLocker al(obj.rlck);
      while (obj.rscan) {
         ++obj.rwaiting;
         obj.rcond.wait(obj.rlck);
         --obj.rwaiting;
      }
      obj.rscan = gettid();
   }

   DLLLOCAL ~RScanHelper() {
      AutoLocker al(obj.rlck);
      assert(obj.rscan == gettid());
      // we have to use broadcast here because the condition variable is shared
      if (obj.rwaiting)
         obj.rcond.broadcast();
      obj.rscan = 0;
   }

   DLLLOCAL bool needScan() const {
      return rcycle == obj.rcycle;
   }
};

RSetHelper::RSetHelper(RObject& obj) {
#ifdef DEBUG
   lcnt = 0;
#endif
   if (q_disable_gc)
      return;

   // if the scan should be deferred
   if (obj.checkDeferScan())
      return;

   printd(QRO_LVL, "RSetHelper::RSetHelper() this: %p (%p %s) ENTER\n", this, &obj, obj.getName());

   RScanHelper rsh(obj);

   while (true) {
      if (checkIntern(obj)) {
         rollback();
         // wait for foreign transaction to finish if necessary
         notifier.wait();

         if (!rsh.needScan()) {
            printd(QRO_LVL, "RSetHelper::RSetHelper() this: %p (%p: %s) TRANSACTION COMPLETE IN ANOTHER THREAD\n", this, &obj, obj.getName());
            return;
         }
         printd(QRO_LVL, "RSetHelper::RSetHelper() this: %p (%p: %s) RESTARTING TRANSACTION: %d\n", this, &obj, obj.getName(), obj.rcycle);
         continue;
      }

      break;
   }

   if (obj.isValid())
      commit(obj);

   printd(QRO_LVL, "RSetHelper::RSetHelper() this: %p (%p) EXIT\n", this, &obj);
}

void RSetHelper::commit(RObject& obj) {
#ifdef DEBUG
   bool has_obj = false;
#endif

   assert(obj.rml.checkRSectionExclusive());

   // invalidate rsets
   for (rs_set_t::iterator i = tr_invalidate.begin(), e = tr_invalidate.end(); i != e; ++i) {
      (*i)->invalidate();
   }

   // unlock rsection
   for (rset_t::iterator i = tr_out.begin(), e = tr_out.end(); i != e; ++i) {
      assert(fomap.find(*i) == fomap.end());
      (*i)->rml.rSectionUnlock();
      deccnt();
   }

   // finalize graph - exit rsection
#ifdef DEBUG
   // DEBUG
   for (omap_t::iterator i = fomap.begin(), e = fomap.end(); i != e; ++i) {
      RObject* tobj = i->first;
      RSet* rs = i->second.rset;
      int rcount = i->second.rcount;
      assert(tr_out.find(tobj) == tr_out.end());
      tobj->setRSet(rs, rcount);

      if (tobj == &obj)
         has_obj = true;
   }

   for (omap_t::iterator i = fomap.begin(), e = fomap.end(); i != e; ++i) {
      RSet* rs = i->second.rset;
      assert(!rs || (rs->size() == rs->getCount()));

      if (!rs)
         continue;
      for (rset_t::iterator ri = rs->begin(), re = rs->end(); ri != re; ++ri) {
         RObject* o = (*ri);
         assert(o->rset == rs);
      }
   }

   for (omap_t::iterator i = fomap.begin(), e = fomap.end(); i != e; ++i) {
      i->first->rml.rSectionUnlock();
      deccnt();
   }
#else
   for (omap_t::iterator i = fomap.begin(), e = fomap.end(); i != e; ++i) {
      i->first->setRSet(i->second.rset, i->second.rcount);
   }
   for (omap_t::iterator i = fomap.begin(), e = fomap.end(); i != e; ++i) {
      i->first->rml.rSectionUnlock();
   }
#endif

   assert(fomap.empty() || has_obj);
   assert(!lcnt);
}

void RSetHelper::rollback() {
   for (omap_t::iterator i = fomap.begin(), e = fomap.end(); i != e; ++i) {
      if (i->second.rset) {
         RSet* r = i->second.rset;
         if (r->pop())
            delete r;
      }
      i->first->rml.rSectionUnlock();
      deccnt();
   }

   // exit rsection of objects in tr_out
   for (rset_t::iterator i = tr_out.begin(), e = tr_out.end(); i != e; ++i) {
      assert(fomap.find(*i) == fomap.end());
      (*i)->rml.rSectionUnlock();
      deccnt();
   }

   fomap.clear();
   ovec.clear();
   tr_out.clear();
   tr_invalidate.clear();

   assert(!lcnt);

#ifdef _POSIX_PRIORITY_SCHEDULING
   sched_yield();
#endif
}
