// -*- Mode: C++; -*-
//                              File      : RDIOplocks.cc
//                              Package   : omniNotify-Library
//                              Created on: 6-Nov-2000
//                              Authors   : gruber
//
//    Copyright (C) 1998-2001 AT&T Laboratories -- Research
//
//    This file is part of the omniNotify library
//    and is distributed with the omniNotify release.
//
//    The omniNotify library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Library General Public
//    License as published by the Free Software Foundation; either
//    version 2 of the License, or (at your option) any later version.
//
//    This library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//    Library General Public License for more details.
//
//    You should have received a copy of the GNU Library General Public
//    License along with this library; if not, write to the Free
//    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
//    02111-1307, USA
//
//
// Description:
//    Implementation of RDIOplocks
//
 
/*
$Log: RDIOplocks.cc,v $
Revision 1.5  2001/06/26 20:01:20  alcfp
updated copyright notices, added support for omniORB4, switched default to POA

Revision 1.4  2001/06/22 07:00:38  alcfp
moved to new logging scheme

Revision 1.3  2000/11/19 19:02:41  alcfp
removed unnecessary debug output

Revision 1.2  2000/11/16 05:32:16  alcfp
fixes for aCC

Revision 1.1  2000/11/15 21:17:30  alcfp
large number of changes to switch to use of RDIOplocks for safe object disposal support.  also reduced code duplication a little, and tried hard to make all the proxy code consistent

*/

#include "RDI.h"
#include "RDIOplocks.h"

// ---------------------- RDIOplockEntry -------------------------------

CORBA::Boolean RDIOplockEntry::acquire(RDIOplockEntry** ptr) {
  if ((ptr != _ptr) || _disposed) {
    return 0;
  }
  _oplock.lock();
  if ((ptr != _ptr) || _disposed) {
    _oplock.unlock();
    return 0;
  }
  return 1;
}

CORBA::Boolean RDIOplockEntry::reacquire(RDIOplockEntry** ptr) {
  if (ptr != _ptr) return 0;
  _oplock.lock();
  if (ptr != _ptr) {
    _oplock.unlock();
    return 0;
  }
  return 1;
}

void RDIOplockEntry::wait() {
  CORBA::Boolean do_bump_debump = 1;
  if (_disposed) {
    do_bump_debump = 0;
    RDIDbgForceLog("** Internal error: RDIOplockEntry " << (void*)this <<
		   " calling wait after disposed is true\n");
  }
  if (do_bump_debump) { _inuse++; } 
  _waitvar.wait();
  if (do_bump_debump) { _inuse--; } 
}

void RDIOplockEntry::timedwait(unsigned long s, unsigned long n) {
  CORBA::Boolean do_bump_debump = 1;
  if (_disposed) {
    do_bump_debump = 0;
    RDIDbgForceLog("** Internal error: RDIOplockEntry " << (void*)this <<
		   " calling timedwait after disposed is true");
  }
  if (do_bump_debump) { _inuse++; } 
  _waitvar.timedwait(s,n);
  if (do_bump_debump) { _inuse--; } 
}

void RDIOplockEntry::bump() {
  if (_disposed) {
    RDIDbgForceLog("** Internal error: RDIOplockEntry " << (void*)this <<
		   " bumping inuse after disposed is true");
  }
  _inuse++;
}

void RDIOplockEntry::debump() {
  if (_inuse == 0) {
    RDIDbgForceLog("** Internal error: RDIOplockEntry " << (void*)this <<
		   " debumping zero inuse -- ignored");
  } else {
    _inuse--;
    if (_inuse == 0) {
      _inusezero.broadcast();
    }
  }
}

// -------------- helper routines -----------------

// remove this from chain -- becomes singleton chain
void RDIOplockEntry::_remove() {
  _prev->_next = _next;
  _next->_prev = _prev;
  _next = this;
  _prev = this;
}

// prepend this in front of b (b is in chain, this is not)
void RDIOplockEntry::_prepend(RDIOplockEntry* b) {
  _next = b;
  _prev = b->_prev;
  _prev->_next = this;
  b->_prev = this;
}

// append this after b (b is in chain, this is not)
void RDIOplockEntry::_append(RDIOplockEntry* b) {
  _prev = b;
  _next = b->_next;
  _next->_prev = this;
  b->_next = this;
}

// ---------------------- RDIOplocks -----------------------------------

// private static class variables

omni_mutex       RDIOplocks::_oplock;
RDIOplockEntry*  RDIOplocks::_freelist = new RDIOplockEntry();
CORBA::Boolean   RDIOplocks::_shutdown = 0;

// cleanup returns 1 if there are entries in use that could not be discarded, else 0
CORBA::Boolean RDIOplocks::cleanup() {
  _oplock.lock();
  if (_freelist == 0) { 
    _oplock.unlock();
    return 0;
  }
  // discard entries with ptr == 0 and inuse == 0
  RDIOplockEntry* nxt_nxt = _freelist;
  for (RDIOplockEntry* nxt = _freelist->_next; nxt != _freelist; nxt = nxt_nxt) {
    nxt_nxt = nxt->_next; 
    if ((nxt->_ptr == 0) && (nxt->_inuse == 0)) {
      nxt->_remove(); // removes from freelist
      delete nxt;
    }
  }
  if (_freelist->_next != _freelist) {
    _oplock.unlock();
    return 1;   // some entries are still in use
  }
  _oplock.unlock();
  return 0;
}

void RDIOplocks::shutdown() {
  _oplock.lock();
  if (_shutdown) { // already called
    _oplock.unlock();
    return;
  }
  _shutdown = 1;
  _oplock.unlock();
  while (cleanup()) {
    // there are still entries to be cleaned up
    omni_thread::yield(); // should put in microsec delay?
  }
  _oplock.lock();
  if (_freelist) {
    delete _freelist;
  }
  _freelist = 0;
  _oplock.unlock();
}

RDIOplockEntry* RDIOplocks::alloc_entry(RDIOplockEntry** optr) {
  if (optr == 0) {
    RDIDbgForceLog("** Internal error: RDIOplocks::alloc_entry called with null ptr");
    return 0;
  }
  _oplock.lock();
  if (_shutdown) {
    _oplock.unlock();
    return 0;
  }
  RDIOplockEntry* nxt = 0;
  // first try to re-use an entry on the free list 
  for (nxt = _freelist->_next; nxt != _freelist; nxt = nxt->_next) {
    if (nxt->_ptr == 0) {
      nxt->_oplock.lock();
      if ((nxt->_ptr != 0) || (nxt->_inuse != 0) || (nxt->_disposed)) {
	RDIDbgForceLog("** Internal error: RDIOplocks::alloc_entry : " <<
		       "once ptr is zero, ptr, inuse, and disposed " <<
		       "are supposed to remain zero until entry is reallocated");
	nxt->_oplock.unlock();
	continue;
      }
      nxt->_remove(); // removes from freelist
      nxt->_dispose_info = WRAPPED_DISPOSEINFO_NIL;
      nxt->_inuse = 0;
      nxt->_disposed = 0;
      nxt->_ptr = optr;
      _oplock.unlock();
      nxt->_oplock.unlock();
      return nxt;
    }
    if (nxt->_disposed && (nxt->_inuse == 0)) {
      nxt->_oplock.lock();
      if (!nxt->_disposed || (nxt->_inuse != 0)) {
	RDIDbgForceLog("** Internal error: RDIOplocks::alloc_entry : " <<
		       "once disposed is set, it is supposed to remain set " <<
		       "and inuse is supposed to remain zero until dispose action occurs");
	nxt->_oplock.unlock();
	continue;
      }
      nxt->_remove(); // removes from freelist
      if (!WRAPPED_DISPOSEINFO_IS_NIL(nxt->_dispose_info)) {
	WRAPPED_DISPOSE_INFO(nxt->_dispose_info);
      }
      nxt->_dispose_info = WRAPPED_DISPOSEINFO_NIL;
      nxt->_inuse = 0;
      nxt->_disposed = 0;
      nxt->_ptr = optr;
      _oplock.unlock();
      nxt->_oplock.unlock();
      return nxt;
    }
    // nxt is 'in use' -- skip
  }
  // none of the entries on the free list are available at the moment
  // so allocate a new entry
  nxt = new RDIOplockEntry();
  nxt->_oplock.lock();
  nxt->_dispose_info = WRAPPED_DISPOSEINFO_NIL;
  nxt->_inuse = 0;
  nxt->_disposed = 0;
  nxt->_ptr = optr;
  _oplock.unlock();
  nxt->_oplock.unlock();
  return nxt;
}

// ASSUMES: e->_oplock has been locked
void RDIOplocks::free_entry(RDIOplockEntry* e, RDIOplockEntry** optr, WRAPPED_DISPOSEINFO_PTR dinfo) {
  if (e->_ptr != optr) {
    RDIDbgForceLog("*** Internal error: RDIOplocks::free_entry called with optr != e->_ptr");
    return;
  }
  if (e->_disposed) {  // it is OUR job to set e->_disposed
    RDIDbgForceLog("*** Internal error: RDIOplocks::free_entry should not be " <<
		   "called with e->_disposed set");
    e->_disposed = 0;
  }
  if ((e->_next != e) || (e->_prev != e)) {
    RDIDbgForceLog("*** Internal error: RDIOplocks::free_entry called with an entry " <<
		   "that appears to already be on the free list");
    e->_remove();
  }
  // append e to free list (by prepending in front of dummy head elt)
  e->_prepend(_freelist);
  if (e->_inuse == 0) { // can dispose immediately
    if (!WRAPPED_DISPOSEINFO_IS_NIL(dinfo)) {
      WRAPPED_DISPOSE_INFO(dinfo);
    }
    e->_ptr = 0;
  } else { // defer disposal
    e->_disposed = 1;
    e->_dispose_info = dinfo;
  }
  e->_oplock.unlock();
}

