CORBA - Reference Counting

| 0 Comments

We've been developing code on Windows and Unix for quite some time, but most of the distributed component work we've been involved in has been done using COM. That's changing now as clients have more requirements for Unix based component solutions. We're currently evaluating CORBA ORBs and learning how the CORBA way of doing things differs from the COM way.

The first difference that we came across was the way that CORBA servers deal with object lifetime issues. Adding reference counting to CORBA objects isn't as easy as it first seems

Please note that these articles initially explore COM concepts implemented in CORBA, some of the early implementations, whilst working examples, aren't at all production quality... If you want something reliable, that works, skip forward to the Evictor Pattern.

In COM you're used to your clients having control over the lifetime of your server objects, reference counting is a way of life and if you need an object that acts as a persistent singleton within a server you can achieve it by altering how the reference counting works in the server. With CORBA the opposite stance is taken. Server object lifetime is completely unrelated to client proxy lifetime. In an ideal, non-deterministic world, some form of garbage collection on the server might reclaim objects for you. Whilst the CORBA books and FAQs admit that you can implement reference counting if you really have to, they never seem to give any examples of how to do it. In fact, adding reference counting to CORBA isn't that hard once you've realised that the obvious ways just don't work. These articles will show you several ways of doing it and then discuss why you probably don't want to.

Be aware that in showing how reference counting could be implemented in CORBA we're going to take the obvious route that a COM programmer might take. In doing so we'll encounter lots of dead ends and frustrations, this is deliberate. The intention is to show that the methods that are so obvious to us as COM programmers just don't cut it in CORBA. Even when we finally get to a point where we have what seems to be a working implementation we'll find out that it's less than ideal. Don't be put off, we do end up with a working and reliable solution to the problem, we just have to approach from a slightly different angle..

A simple example
Suppose we have a server that hosts a single factory object. The factory object can be asked for instances of a named counter object. Each named counter is just a value with a name associated with it. The IDL for the server could be as follows:

interface NamedCounter
{
   readonly attribute string name ;
   attribute long value;
};

interface NamedCounterFactory
{
   exception CounterExists 
   {
      string name;
   };

   exception NoCounterExists 
   {
      string name;
   };

   typedef sequence<namedcounter> counterSeq;

   NamedCounter GetNamedCounter(
      in string name) raises (NoCounterExists);

   void AddNamedCounter(
      in string name, 
      in long initialValue) raises (CounterExists);

   void RemoveNamedCounter(
      in string name) raises (NoCounterExists);

   counterSeq GetAll();
};

The server registers the NamedCounterFactory object with the naming service and clients can obtain a reference to it by name. The client can then list the current counters using GetAll(). They can add, remove and lookup counters by name. The client also allows you to hold onto a reference to an object for a period of seconds. Run the client without any command line parameters to find out how to use it. The implementation is fairly simple and a fully working example can be found in the downloadable file RefCounted1.zip.

There are a few problems with this initial implementation. One is that if you run the server and then run a client and add a counter using the command "client add counter"; then run a client in another window and have it hold onto a counter reference for 10 seconds, using "client hold counter 10". If you then switch back to the first client and run it again to delete the counter, using "client del counter" an exception is thrown on the server because a servant that is currently active has been deleted. Also, when the client that is holding a reference to the now deleted counter times out, it attempts to access the counter and another exception is thrown. This second exception is caused by the counter servant no longer existing in the server.

If we remove the line that deletes the named counter servant object when the client deletes the named counter then these problems go away, but the server is now leaking memory. In a Java implementation the runtime may eventually decide to clean up the leaked objects - but don't count on it, the objects are still being referenced within the server, and when we're working in C++ the memory is gone for good anyway and this could eventually lead to the server crashing due to a low memory situation.

Breaking the bond
There are at least two things wrong with the approach taken in RefCounted1.zip. Firstly we're deleting a servant to a CORBA object that is still active. Secondly there's no way of knowing if anyone else is using the CORBA object when we call delete on the servant object. We can attempt to address the first issue by deactivating the CORBA object before we delete the associated servant object. We'll address the second issue with explicit, COM style, client side reference counting a little later.

By default the Portable Object Adapter that is managing our servants has the RETAIN policy value set. This means that the POA maintains an Active Object Map for us. A POA's Active Object Map is an association between the CORBA object's ID and the servant that is implementing that object. It's used by the POA during method call dispatch to locate the appropriate servant object for a given object ID. Having a servant delete itself without the POA's permission means that the POA now has an object ID that maps to a servant that doesn't exist anymore. The POA doesn't know that the servant has been deleted and so probably crashes your program if it tries to route any more calls to the object concerned.

To be able to delete the servant object you must first deactivate the associated CORBA object and thus sever the link between the CORBA object's ID and the servant object that you wish to delete. You can do this by calling deactivate_object on the POA that you activated your object in, and passing the object ID to identify the CORBA object that you wish to deactivate. Your servant object needs to keep track of the POA that it was activated in, and the object ID of the CORBA object that it's providing an implementation for.

In RefCounted2.zip we adjust the implementation to call deactivate_object in the destructor of the servant object. This removes the exception caused by the CORBA object still being active when the servant object is deleted but as we see later, the POA can still crash our server.

AddRef and Release
To solve the problem of deleting an object when references to it are being held, requires that we allow the servant object to know how many references to it are in existence and only allow itself to be deleted if the count of outstanding references falls to 0. Note that by doing this we're handing over control of our server-side object to the client, they may never release them, or may acquire more objects than they need in an attempt to bring the server down. Anyway, we'll ignore this for now, in RefCounted3.zip we alter the interface to the named counter object so that includes support for two explicit reference counting methods.

The resulting IDL now looks like this:

interface NamedCounter
{
   readonly attribute string name;

   attribute long value;

void AddRef(); void Release(); };

Now, when ever a named counter object has a reference passed out AddRef() should be called and whenever that reference is no longer required Release() should be called. Methods on the named counter factory object that hand out references to a named counter should call AddRef() before returning the reference to the client; the client should call AddRef() each time it makes a copy of a named counter reference and Release() each time it is finished with a reference. This approach will be familiar to us as COM programmers.

The implementation of AddRef() and Release() should be pretty simple.

void NamedCounterImpl::AddRef()
{
   m_refCount++;
}

void NamedCounterImpl::Release()
{
   if (--m_refCount == 0)
   {
      delete this;
   }
}

We'll ignore the lack of thread safe access to the reference count for now, it's the least of our worries.

Unfortunately, for CORBA servants, this approach wont work. The POA is managing the dispatch of method calls to our servant object and it's also managing the Active Object Map. Even if we call deactivate_object to deactivate the object and remove it from the Active Object Map before the servant object is destroyed we will still have problems due to how the POA deals with deactivated objects. Even though we deactivate the object before we delete it, the POA will expect the object to be around until after the method call completes.

A servant that uses the POA must be derived from PortableServer::ServantBase. This provides some base functionality that the POA requires to manipulate the servant object whilst it's managing it. Part of the functionality is to include support for a form of reference counting, this reference counting is purely server side. The POA assumes that our servant object lifetime is managed by the reference counting scheme supplied by PortableServer::ServantBase and calls _add_ref() and _remove_ref() methods when the object is manipulated. The POA calls _add_ref() when the servant object is activated and the corresponding call to _remove_ref() is not made until the servant is deactivated and removed from the Active Object Map. This reference counting is used to allow servant objects to be created on the heap in the server, activated and handed out to clients, and then automatically cleaned up when the server deactivates them. Unfortunately, this isn't quite the kind of reference counting that we require to keep a CORBA object active whilst it has clients that refer to it.

Since the POA doesn't require that all objects are allocated on the heap, the reference counting methods in ServantBase are actually no ops. All servant objects implement them, but unless you derive your servant from PortableServer::RefCountServantBase they do nothing. This allows server developers to choose how the lifetime of their servants is managed. Heap based servants should inherit from RefCountServantBase, stack based servants shouldn't.

When deactivate_object is called the POA prevents any future method calls on the servant object, allows all current method calls to complete, and then deactivates the object. First the servant object is remove from the Active Object Map and then a call _remove_ref() is made to to handle reference counted, heap-based servants derived from RefCountServantBase. Because of this, calling deactivate_object and then deleting the servant will cause an access violation when _remove_ref() is called as the object is deactivated after the method call that did the delete returns to the POA.

It's also worth noticing the changes that are required in the client code when we use explicit reference counting. As a COM programmer we can probably ignore it because it's so normal looking, but we need to release the references to the counter objects returned when we call GetAll() and GetNamedCounter() and if we were to pass references about within the client we would need to call AddRef() in appropriate places too...

Using RefCountServantBase
It would be nice to be able to implement AddRef() and Release() something like this.

void NamedCounterImpl::AddRef()
{
   RefCountServantBase::_add_ref();
}

void NamedCounterImpl::Release()
{
   RefCountServantBase::_release_ref();
}

After all, the code in RefCountServantBase does the right thing. Unfortunately we need to know when to deactivate the object as deactivating the object removes the final reference. To be able to know that we need to be able to peek at the reference count inside of RefCountServantBase, and we can't do that because it's a private data member. The reference count maintained in RefCountServantBase is for server side references, it's not supposed to be hijacked like this for client side reference counting...

We can't do clever things; like call deactivate_object in the object's destructor as the call to deactivate_object must occur to initiate servant destruction, and the method that made the deactivate_object call must return to the POA before the servant object is actually deleted.

This results in us having to keep our own reference count and do something like this:

void NamedCounterImpl::AddRef()
{
   m_refCount++;
}

void NamedCounterImpl::Release()
{
   if (--m_refCount == 0)
   {
      RefCountServantBase::_remove_ref();
      m_poa->deactivate_object(m_objectID);
}

Notice that the extra call to _remove_ref() is required because we increment the reference count when we first activate our object - the common CORBA way to deal with this is to activate the object and then pass ownership of it to the POA by calling _remove_ref() straight away. So we could move the extra call to _remove_ref() up into the object's constructor, where the object activation occurs, but for some reason that looks really weird to me and I prefer it to be in the release call...

The resulting solution is presented in RefCounted4.zip. We now have two reference counts, one in RefCountServantBase to handle the lifetime of the servant object based on references held inside the server process and one in our own object to handle client references. In addition, our own object needs to keep track of its object ID and the POA that it was created in - this complicates the object's constructor; and also, the POA that the servant was activated in must have the RETAIN policy set and it shouldn't have a custom servant locator or servant activator assigned to it.

All in all, it seems that the solution we have here is fragile and complicated to manage. It does work though, so it's a start... We'll address some of these problems in the next article.

Download
The following source was built using Visual Studio 6.0 SP3 and tested with OmniORB - the open source Corba ORB from AT&T Cambridge. You need to add OMNI_HOME to your environment so that the idl compiler, headers and libraries can be found.

Get OmniORB
RefCounted1.zip - the simple example with no reference counting
RefCounted2.zip - try deactivating the object first
RefCounted3.zip - add reference counting methods to the interface
RefCounted4.zip - another attempt

Revision history

  • 6th February 2001 - Initial revision at www.jetbyte.com.
  • 15th March 2001 - adjusted the project files so that they use the OMNI_HOME environment variable to pick up the idl compiler, headers and libraries.
  • 21st July 2004 - Reprinted at www.lenholgate.com.

Leave a comment