// -*- Mode: C++; -*-
//                              File      : RDIOplocks.h
//                              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:
//    proprietary interface
//
 
/*
$Log: RDIOplocks.h,v $
Revision 1.3  2001/09/06 15:28:45  alcfp
changed event channel to use oplockptr

Revision 1.2  2001/06/26 20:01:09  alcfp
updated copyright notices, added support for omniORB4, switched default to POA

Revision 1.1  2000/11/15 21:18:17  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

*/
 
#ifndef _RDI_OPLOCKS_H_
#define _RDI_OPLOCKS_H_

#include "omnithread.h"
#include "corba_wrappers.h"

// ----------------------------------------------------------- //
// Portable support for deallocating objects that might be
// "in use" at the time a deallocate request occurs.
// ----------------------------------------------------------- // 

// See RDIOplocksMacros.h for useful macros related to RDIOplocks

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

// INVARIANTS:
//    ptr is NULL => inuse is zero and will remain zero until entry is re-assigned
//    inuse > 0 => ptr is non-NULL
//    disposed is 1, inuse > 0
//            => inuse should not be incremented anymore, only decremented
// TRANSITIONS:
//    inuse goes from 1 to 0
//            => this causes an inusezero.broadcast()
//    disposed is 1, inuse goes from 1 to 0
//            => this triggers a dispose action which causes a transition
//               to uninitialized state where ptr, inuse, disposed are all zero

class RDIOplocks;

class RDIOplockEntry {
public:
  RDIOplockEntry() : _oplock(), _waitvar(&_oplock), _inusezero(&_oplock),
		     _inuse(0), _ptr(0), _disposed(0)
  {
    _prev = this;
    _next = this;
    _dispose_info = WRAPPED_DISPOSEINFO_NIL;
  }

  // add_condition associates a new condition variable with _oplock
  // caller is responsible for deleting when it is no longer needed
  omni_condition* add_condition() { return new omni_condition(&_oplock); }

  // * acquire only acquires lock if _ptr and ptr are equal and _disposed is false
  // * reacquire only acquires lock if _ptr and ptr are equal
  ///   (each returns true if the lock is acquired, else false) 
  // * release releases the lock (must be held)
  CORBA::Boolean acquire(RDIOplockEntry** ptr);
  CORBA::Boolean reacquire(RDIOplockEntry** ptr);
  void release()         { _oplock.unlock(); }   
  // these methods should only be called while holding oplock
  void bump();
  void debump();
  void signal()          { _waitvar.signal(); } 
  void broadcast()       { _waitvar.broadcast(); }
  void wait();
  void timedwait(unsigned long s, unsigned long n);
  void inusezero_wait()  {_inusezero.wait(); }
  void inusezero_timedwait(unsigned long s, unsigned long n)
                         {_inusezero.timedwait(s,n); }
  // these state access methods should be called while holding oplock 
  // if accurate values are required.
  CORBA::UShort  inuse()    { return _inuse; }
  CORBA::Boolean disposed() { return _disposed; }
  RDIOplockEntry** ptr()    { return _ptr; }         

private:
  friend class RDIOplocks;
  omni_mutex               _oplock;
  omni_condition           _waitvar;
  omni_condition           _inusezero;
  CORBA::UShort            _inuse;
  RDIOplockEntry**         _ptr;
  CORBA::Boolean           _disposed;
  WRAPPED_DISPOSEINFO_VAR  _dispose_info;

  // for chaining entry on a doubly-linked list
  RDIOplockEntry*          _prev;
  RDIOplockEntry*          _next;

  // remove this from list (becomes singleton list)
  void _remove();
  // prepend this in front of b (b is in chain, this is not)
  void _prepend(RDIOplockEntry* b);
  // append this after b (b is in chain, this is not)
  void _append(RDIOplockEntry* b);

};

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

class RDIOplocks {
public:
  static RDIOplockEntry* alloc_entry(RDIOplockEntry** optr);
  static void            free_entry(RDIOplockEntry* e, RDIOplockEntry** optr, WRAPPED_DISPOSEINFO_PTR dinfo);

  // * alloc_entry returns an entry whose ptr is
  //   set to optr (normally '(&_oplockptr)' is used).
  //   (the entry is not locked)

  // * free_entry should be called while holding e's oplock
  //   and it should only be called by the object whose ptr
  //   is stored in ptr -- we pass in optr to allow for a sanity check.
  //   A side-effect is that e's oplock is released.
  //   Another side-effect: if a non-nil dinfo is passed to free_entry,
  //   then RDIOplocks is in charge of disposal.  If inuse is zero disposal
  //   happens immediately, otherwise it is deferred; disposed is set to 1.
  //
  //   Once disposal has occurred, ptr is changed either
  //   to zero or to some other value, thus an entry's user can compare
  //   ptr to '(&_oplockptr)' to ensure the entry still 'belongs' to it. 
  //   The RDI_OPLOCK_PREAMBLE macro above performs this check and
  //   obtains a lock on oplock.

  static CORBA::Boolean cleanup();
  static void shutdown();

  // * cleanup returns 1 if there are entries in use that could not be discarded, else 0

  // * shutdown disables further allocations and calls cleanup until
  //   there are no more entries, then it deletes the dummy entry.
  //   shutdown is only useful if there is a need
  //   to delete all allocated storage, e.g., for memory leak checking.

private:
  static omni_mutex       _oplock;
  static RDIOplockEntry*  _freelist;
  static CORBA::Boolean   _shutdown;
};

#endif
