[Up: Implementation Overview]
[Previous: BOA] [Next: IDL Compiler]

Subsections


POA

The Basic Object Adapter provides a bare minimum of functionality to server applications. As a consequence, many ORBs added custom extensions to the BOA to support more complex demands upon an object adapter, making server implementations incompatible among different ORB vendors. In CORBA 2.2, the new Portable Object Adapter was introduced. It provides a much-extended interface that addresses many needs that were wished for, but not available with the original BOA specification. POA features include:

These features, make the POA much more powerful than the BOA and should fulfill most server applications' needs. As an example, object references for some million entries in a database can be generated, which are all implemented by a single default servant.


Architecture

The general idea is to have each server contain a hierarchy of POAs. Only the Root POA is created by default; a reference to the Root POA is obtained using the resolve_initial_references() operation on the ORB. New POAs can be created as the child of an existing POA, each with its own set of policies.

Each POA maintains an Active Object Map that maps all objects that have been activated in the POA to a servant. For each incoming request, the POA looks up the object reference in the Active Object Map and tries to find the responsible servant. If none is found, the request is either delegated to a default servant, or a servant manager is invoked to activate or locate an appropriate servant.

Associated with each POA is a POA Manager object. A POA Manager can control one or many POAs. For each incoming request to an object, the POA Manager's state is checked, which can be one of the following:

Active
 
Requests are performed immediately.
Holding
 
Incoming requests are queued. This is the initial state of a POA Manager; to perform requests, the POA Manager must be explicitely set to the Active state.
Discarding
 
Requests are discarded. Clients receive a TRANSIENT exception.
Inactive
 
This is the ``final'' state of a POA Manager, which is entered prior to destruction of the associated POAs. Clients receive an OBJ_ADAPTER exception.

Before continuing, we should more precisely define a few terms that have already been freely used.

Object Reference
 
On the client side, an object reference encapsulates the identity of a distinct abstract object. On the server side, an object reference is composed of the POA identity in which the object is realized, and a Object Id that uniquely identifies the object within the POA.
Object Id
 
An Object Id is an opaque sequence of octets. Object Ids can be either system generated (the POA assigns a unique Id upon object activation), or user generated (the user must provide an Id upon object activation). The object's Object Id cannot be changed through the object's lifetime.

In many cases, object references and Object Id can be used synonymously, since an object reference is just an Object Id with opaque POA-added ``internal'' information.

Servant
 
A servant provides the implementation for one or more object references. In the C++ language mapping, a servant is an instance of a C++ class that inherits from PortableServer::ServantBase. This is true for dynamic skeleton implementations (DSI), or for classes that inherit from IDL-generated skeletons.

The process of associating a servant with an Object Id is called activation and is performed using POA methods. A servant can be activated more than once (to serve many different Object Ids) and can be activated in many POAs. After activation, object references can be obtained using other POA methods.

Servants are not objects and do not inherit from CORBA::Object. It is illegal to perform operations directly upon a servant - all invocations must be routed through the ORB. Also, memory management of servants is entirely left to the user. POAs keep only a pointer to a servant, so they must not be deleted while being activated.

Server
 
``Server'' refers to a complete process in which servants exist. A server can contain one or more POAs, each of which can provide zero, one or more active servants. Each active servant can then serve one or more object references.


Policies

We have already mentioned the policies that control various aspects of POA behaviour. POA policies do not change over the POA's lifetime. When creating a new POA as a child of an existing POA, policies are not inherited from the parent, but instead each POA is assigned a set of default policies if not explicitely defined.

Thread Policy
 
ORB_CTRL_MODEL
(default)
Invocations are performed as scheduled by the ORB. Potentially, many upcalls are perfomed simultaneously.
SINGLE_THREAD_MODEL
 
Invocations are serialized. At most a single upcall is performed at any time.
Non-reentrant servants should only be activated in POAs with the SINGLE_THREAD_MODEL policy.

As the current version of MICO is not multithreaded, this policy is not yet evaluated.

Lifespan Policy
 
TRANSIENT
(default)
Objects activated in this POA cannot outlive the server process.
PERSISTENT
 
Objects can outlive the server process
Id Uniqueness Policy
 
UNIQUE_ID
(default)
Servants can be activated at most once in this POA.
MULTIPLE_ID
 
Servants can be activated more than once in this POA and can therefore serve more than one object reference.
Id Assignment Policy
 
SYSTEM_ID
(default)
Object Ids are assigned by the POA upon object activation.
USER_ID
 
Upon activation, each servant must be provided with a unique Id by the user.
Servant Retention Policy
 
RETAIN
(default)
The POA maintains a map of active servants (the Active Object Map).
NON_RETAIN
 
The POA does not maintain an Active Object Map.
Request Processing Policy
 
USE_ACTIVE_OBJECT_MAP_ONLY
(default)
To process an incoming request, the object reference is looked up in the Active Object Map only. If no active servant serving the reference is found, the request is rejected, and an OBJECT_NOT_EXIST exception is returned.
USE_DEFAULT_SERVANT
 
The object reference is looked up in the Active Object Map first. If no active servant is found to serve the reference, the request is delegated to a default servant.
USE_SERVANT_MANAGER
 
The object reference is looked up in the Active Object Map first. If no active servant is found to serve the reference, a servant manager is invoked to locate or incarnate an appropriate servant.
Implicit Activation Policy
 
IMPLICIT_ACTIVATION
 
If an inactive servant is used in a context that requires the servant to be active, the servant is implicitly activated.
NO_IMPLICIT_ACTIVATION
(default) 
It is an error to use an inactive servant in a context that requires an active servant.

The Root POA has the ORB_CTRL_MODEL, TRANSIENT, UNIQUE_ID, SYSTEM_ID, RETAIN, USE_ACTIVE_OBJECT_MAP_ONLY and IMPLICIT_ACTIVATION policies.

Example

As an example, let's write a simple POA-based server. You can find the full code in the demo/poa/hello-1 directory in the MICO distribution. Imagine a simple IDL description in the file ``hello.idl'':

  interface HelloWorld {
    void hello ();
  };

The first step is to invoke the IDL to C++ compiler in a way to produce skeleton classes that use the POA:

  idl hello.idl

The IDL compiler will generate POA-baed skeletons by default. Next, we rewrite the server.

 1: // file server.cc
 2:
 3: #include "hello.h"
 4:
 5: class HelloWorld_impl : virtual public POA_HelloWorld
 6: {
 7:   public:
 8:     void hello() { printf ("Hello World!\n"); };
 9: };
10: 
11: 
12: int main( int argc, char *argv[] )
13: {
14:   CORBA::ORB_var orb = CORBA::ORB_init (argc, argv, "mico-local-orb");
15:   CORBA::Object_var poaobj = orb->resolve_initial_references ("RootPOA");
16:   PortableServer::POA_var poa = PortableServer::POA::_narrow (poaobj);
17:   PortableServer::POAManager_var mgr = poa->the_POAManager();
18:
19:   HelloWorld_impl * servant = new HelloWorld_impl;
20:
21:   PortableServer::ObjectId_var oid = poa->activate_object (servant);
22:
23:   mgr->activate ();
24:   orb->run();
25:
26:   poa->destroy (TRUE, TRUE);
27:   delete servant;
28:   return 0;
29: }

The object implementation does not change much with respect to a BOA-based one, the only difference is that HelloWorld_impl does not inherit from the BOA-based skeleton HelloWorld_skel any more, but from the POA-based skeleton POA_HelloWorld.

In main(), we first initialize the ORB, then we obtain a reference to the Root POA (lines 15-16) and to its POA Manager (line 17).

Then, we create an instance of our server object. In line 21, the servant is activated. Since the Root POA has the SYSTEM_ID policy, a unique Object Id is generated automatically and returned. At this point, clients can use the MICO binder to connect to the HelloWorld object.

However, client invocations upon the HelloWorld object are not yet processed. The Root POA's POA Manager is created in the holding state, so in line 23, we transition the POA Manager, and therefore the Root POA, to the active state. We then enter the ORB's event loop in 24.

In this example, run() never returns, because we don't provide a means to shut down the ORB. If that ever happened, lines 26-27 would first destroy the Root POA. Since that deactivates our active HelloWorld object, we can then safely delete the servant.

Since the Root POA has the IMPLICIT_ACTIVATION policy, we can also use several other methods to activate the servant instead of activate_object(). We could, for example, use servant_to_reference(), which first implicitly activates the inactive servant and then returns an object reference pointing to the servant. Or, we could invoke the servant's inherited _this() method, which also implicitly activates the servant and returns an object reference.

Using a Servant Manager

While the previous example did introduce the POA, it did not demonstrate any of its abilities - the example would have been just as simple using the BOA.

As a more complex example, we want to show a server that generates ``virtual'' object references that point to non-existent objects. We then provide the POA with a servant manager that incarnates the objects on demand.

We continue our series of ``Account'' examples. We provide the implementation for a Bank object with a single ``create'' operation that opens a new account. However, the Account object is not put into existence at that point, we just return a reference that will cause activation of an Account object when it is first accessed. This text will only show some code fragments; find the full code in the demo/poa/account-2 directory.

The implementation of the Account object does not differ from before. More interesting is the implementation of the Bank's create operation:

  Account_ptr
  Bank_impl::create ()
  {
    CORBA::Object_var obj = mypoa->create_reference ("IDL:Account:1.0");
    Account_ptr aref = Account::_narrow (obj);
    assert (!CORBA::is_nil (aref));
    return aref;
  }

The create_reference() operation on the POA does not cause an activation to take place. It only creates a new object reference encapsulating information about the supported interface and a unique (system-generated) Object Id. This reference is then returned to the client.

Now, when the client invokes an operation on the returned reference, the POA will first search its Active Object Map, but will find no servant to serve the request. We therefore implement a servant manager, which will be asked to find an appropriate implementation.

There are two types of servant managers: a Servant Activator activates a new servant, which will be retained in the POA's Active Object Map to serve further requests on the same object. A Servant Locator is used to locate a servant for a single invocation only; the servant will not be retained for future use. The type of servant manager depends on the POA's Servant Retention policy.

In our case, we use a servant activator, which will incarnate and activate a new servant whenever the account is used first. Further operations on the same object reference will use the already active servant. Since the create_reference() operation uses a unique Object Id each time it is called, one new servant will be incarnated for each Account - this represents the BOA's Unshared activation mode.

A servant activator provides two operations, incarnate and etherealize. The former one is called when a new servant needs to be incarnated to serve a previously unknown Object Id. etherealize is called when the servant is deactivated (for example in POA shutdown) and allows the servant manager to clean up associated data.

  class AccountManager : public virtual POA_PortableServer::ServantActivator
  { /* declarations */ };

  PortableServer::Servant
  AccountManager::incarnate (/* params */)
  {
    return new Account_impl;
  }

  void
  AccountManager::etherealize (PortableServer::Servant serv,
                               /* many more params */)
  {
    delete serv;
  }

Our servant activator implements the POA_PortableServer::ServantActivator interface. Since servant managers are servants themselves, they must be activated like any other servant (see below).

The incarnate operation has nothing to do but to create a new Account servant. incarnate receives the current POA and the requested Object Id as parameters, so it would be possible to perform special initialization based on the Object Id that is to be served.

etherealize is just as simple, and deletes the servant. In ``real life'', the servant manager would have to make sure that the servant is not in use anywhere else before deleting it. Here, this is guaranteed by our program logic.

The main() code is a little more extensive than before. Because the Root POA has the USE_ACTIVE_OBJECT_MAP_ONLY policy and does not allow a servant manager, we must create our own POA with the USE_SERVANT_MANAGER policy.

  CORBA::ORB_var orb = CORBA::ORB_init (argc, argv, "mico-local-orb");
  CORBA::Object_var poaobj = orb->resolve_initial_references ("RootPOA");
  PortableServer::POA_var poa = PortableServer::POA::_narrow (poaobj);
  PortableServer::POAManager_var mgr = poa->the_POAManager();

  CORBA::PolicyList pl;
  pl.length(1);
  pl[0] = poa->
    create_request_processing_policy (PortableServer::USE_SERVANT_MANAGER);
  PortableServer::POA_var mypoa = poa->create_POA ("MyPOA", mgr, pl);

Note that we use the Root POA's POA Manager when creating the new POA. This means that the POA Manager has now control over both POAs, and changing its state affects both POAs. If we passed NULL as the second parameter to create_POA(), a new POA Manager would have been created, and we would have to change both POA's states separately.

We can now register the servant manager.

  AccountManager * am = new AccountManager;
  PortableServer::ServantManager_var amref = am->_this ();
  mypoa->set_servant_manager (amref);

After creating an instance of our servant manager, we obtain an object reference using the inherited _this() method. This also implicitly activates the servant manager in the Root POA.

  Bank_impl * micocash = new Bank_impl (mypoa);
  PortableServer::ObjectId_var oid = poa->activate_object (micocash);
  mgr->activate ();
  orb->run();

Now the only thing left to do is to activate a Bank object, to change both POAs to the active state, and to enter the ORB's event loop.

Persistent Objects

Our previous examples used ``transient'' objects which cannot outlive the server process they were created in. If you write a server that activates a servant and export its object reference, and then stop and re-start the server, clients will receive an exception that their object reference has become invalid.

In many cases it is desirable to have persistent objects. A persistent object has an infinite lifetime, not bound by the process that implements the object. You can kill and restart the server process, for example to save resources while it is not needed, or to update the implementation, and the client objects will not notice as long as the server is running whenever an invocation is performed.

An object is persistent if the servant that implements them is activated in a POA that has the PERSISTENT lifespan policy.

As an example, we will expand our Bank to create persistent accounts. When the server goes down, we want to write the account balances to a disk file, and when the server is restarted, the balances are read back in. To accomplish this, we use a persistent POA to create our accounts in. Using a servant manager provides us with the necessary hooks to save and restore the state: when etherealizing an account, the balance is written to disk, and when incarnating an account, we check if an appropriately named file with a balance exists.

We also make the Bank itself persistent, but use a different POA to activate the Bank in. Of course, we could use the Accounts' POA for the Bank, too, but then, our servant manager would have to discriminate whether it is etherealizing an Account or a Bank: using a different POA comes more cheaply.

The implementation of the Account object is the same as in the previous examples. The Bank is basically the same, too. One change is that the create operation has been extended to activate accounts with a specific Object Id - we will use an Account's Object Id as the name for the balance file on disk.

We also add a shutdown operation to the Bank interface, which is supposed to terminate the server process. This is accomplished simply by calling the ORB's shutdown method:

  void
  Bank_impl::shutdown (void)
  {
    orb->shutdown (TRUE);
  }

Invoking shutdown() on the ORB first of all causes the destruction of all object adapters. Destruction of the Account's POA next causes all active objects - our accounts - to be etherealized by invoking the servant manager. Consequently, the servant manager is all we need to save and restore our state.

One problem is that the servant manager's etherealize() method receives a PortableServer::Servant value. However, we need access to the implementation's type, Account_impl*, to query the current balance. Since CORBA does not provide narrowing for servant types, we have to find a solution on our own. Here, we use an STL map mapping the one to the other:4.14

  class Account_impl;
  typedef map<PortableServer::Servant,
    Account_impl *,
    less<PortableServer::Servant> > ServantMap;
  ServantMap svmap;

When incarnating an account, we populate this map; when etherealizing the account, we can retrieve the implementation's pointer.

  PortableServer::Servant
  AccountManager::incarnate (/* params */)
  {
    Account_impl * account = new Account_impl;
    CORBA::Long amount = ...  // retrieve balance from disk
    account->deposit (amount);

    svmap[account] = account; // populate map
    return account;
  }

  void
  AccountManager::etherealize (PortableServer::Servant serv,
                               /* many more params */)
  {
    ServantMap::iterator it = svmap.find (serv);
    Account_impl * impl = (*it).second;
    ... // save balance to disk
    svmap.erase (it);
    delete serv;
  }

Please find the full source code in the demo/poa/account-3 directory.

One little bit of magic is left to do. Persistent POAs need a key, a unique ``implementation name'' to identify their objects with. This name must be given using the -POAImplName command line option:4.15

  ./server -POAImplName Bank

Now we have persistent objects, but still have to start up the server by hand. It would be much more convenient if the server was started automatically. This can be achieved using the MICO Daemon (micod) (see section 4.3.2).

For POA-based persistent servers, the implementation repository entry must use the ``poa'' activation mode, for example

  imr create Bank poa ./server IDL:Bank:1.0

The second parameter to imr, Bank, is the same implementation name as above; it must be unique within the implementation repository. If a persistent POA is in contact with the MICO Daemon, object references to a persistent object, when exported from the server process, will not point directly to the server but to the MICO Daemon. Whenever a request is received by micod, it checks if your server is running. If it is, the request is simply forwarded, else a new server is started.

Usually, the first instance of your server must be started manually for bootstrapping, so that you have a chance to export object references to your persistent objects. An alternative is to use the MICO Binder: the IDL:Bank:1.0 in the command line above tells micod that bind() requests for this repository id can be forwarded to this server - after starting it.

With POA-based persistent objects, you can also take advantage of the ``iioploc:'' addressing scheme that is introduced by the Interoperable Naming Service. Instead of using a stringified object reference, you can use a much simpler, URL-like scheme. The format for an iioploc address is

  iioploc://<host>:<port>/<object-key>

host and port are as given with the -ORBIIOPAddr command-line option, and the object key is composed of the implementation name, the POA name and the Object Id, separated by slashes. So, if you start a server using

  ./server -ORBIIOPAddr inet:thishost:1234 -POAImplName MyService

create a persistent POA with the name ``MyPOA'', and then activate an object using the ``MyObject'' Object Id, you could refer to that object using the IOR

  iioploc://thishost:1234/MyService/MyPOA/MyObject

These ``iioploc'' addresses are understood and translated by the string_to_object() method and can therefore be used wherever a stringified object reference can be used.

For added convenience, if the implementation name, the POA name and the Object Id are the same, they are collapsed into a single string. An example for this is the NameService implementation, which uses the ``NameService'' implementation name. The root naming context is then activated in the ``NameService'' POA using the ``NameService'' ObjectId. Consequently, the NameService can be addressed using

  iioploc://<host>:<port>/NameService

Please see the Interoperable Naming Service specification for more details.

Reference Counting

With the POA, implementations do not inherit from CORBA::Object. Consequently, memory management for servants is the user's responsibility. Eventually, a servant must be deleted with C++'s delete operator, and a user must know when a servant is safe to be deleted - deleting a servant that is still known to a POA leads to undesired results.

CORBA 2.3 addresses this problem and introduces reference counting for servants. However, to maintain compatibility, this feature is optional and must be explicitly activated by the user. This is done by adding POA_PortableServer::RefCountServantBase as a base class of your implementation:

class HelloWorld_impl :
  virtual public POA_HelloWorld
  virtual public PortableServer::RefCountServantBase
{
  ...
}

This activates two new operations for your implementation, _add_ref() and _remove_ref(). A newly constructed servant has a reference count of 1, and it is deleted automatically once its reference count drops to zero. This way, you can, for example, forget about your servant just after it has been created and activated:

  HelloWorld_impl * hw = new HelloWorld_impl;
  HelloWorld_var ref = hw->_this(); // implicit activation
  hw->_remove_ref ();

During activation, the POA has increased the reference count for the servant, so you can remove your reference immediately afterwards. The servant will be deleted automatically once the object is deactivated or the POA is destroyed. Note, however, that once you introduce reference counting, you must keep track of the references yourself: All POA operations that return a servant (i.e. id_to_servant() will increase the servants' reference count. The PortableServer::ServantBase_var class is provided for automated reference counting, acting the same as CORBA::Object_var does for Objects.


[Previous: BOA] [Next: IDL Compiler]
[Up: Implementation Overview]

Frank Pilhofer
2001-09-28