This chapter describes the BOA implementation in omniORB2. The CORBA specification defines the Basic Object Adaptor as the entity that mediates between object implementations and the ORB. Unfortunately, the BOA specification is incomplete and does not address the multi-threading issues appropriately. The end result is that different ORB vendors implement different extensions to their BOAs. Worse, the implementation of the operations defined in the specification are different in different ORBs. Recently, a new Object Adaptor specification (the Portable Object Adaptor- POA) has been adopted and will replace the BOA as the standard Object Adaptor in CORBA. The new specification recognises the compatibility problems of BOA and recommends that all BOAs should be considered propriety extensions. OmniORB2 will support POA in future releases. Until then, you have to use the BOA to attach object implementations to the ORB.
The rest of this chapter describes the interface of the BOA in detail. It is important to recognise that the interface described below is omniORB2 specific and hence the code using this interface is unlikely to be portable to other ORBs.
Unless it is stated otherwise, the term ``object'' will be used below to refer to object implementations. This should not be confused with ``object references'' which are handles held by clients.
It takes two steps to put the BOA into service. The BOA has to be initialised using BOA_init and activated using impl_is_ready.
BOA_init is a member of the CORBA::ORB class. Its signature is:
BOA_ptr BOA_init(int & argc, char ** argv, const char * boa_identifier);
Typically, it is used in the startup code as follows:
CORBA::ORB_ptr orb = CORBA::ORB_init(argc,argv,"omniORB2"); // line 1 CORBA::BOA_ptr boa = orb->BOA_init(argc,argv,"omniORB2_BOA"); // line 2
The argv parameters may contain BOA options. These options will be removed from the argv list when BOA_init returns. Other parameters in argv will remain. The supported options are:
If the third argument of BOA_init is non-nil, it must be the string constant ``omniORB2_BOA''. If the argument is nil, -BOAid must be present in argv.
If there is any problem in the initialisation process, a CORBA::INITIALIZE exception would be raised.
To register an object with the BOA, the method _obj_is_ready should be called with the return value of BOA_init as the argument.
BOA_init is thread-safe. It can be called multiple times and the same BOA_ptr will be returned. However, only the argv in the first call will be scanned, the argument is ignored in subsequent calls.
BOA_init returns a pseudo object of type CORBA::BOA_ptr. Similar to CORBA::Object_ptr, the pointer can be managed using CORBA::BOA_var, BOA::_duplicate and CORBA::release. The pointer can be tested using CORBA::is_nil which returns true if the pointer is equivalent to the return value of BOA::_nil.
After BOA_init is called, objects can be registered. However, incoming IIOP requests would not be despatched until impl_is_ready is called.
class BOA { public: impl_is_ready(CORBA::ImplementationDef_ptr p = 0, CORBA::Boolean NonBlocking = 0); };
One of the common pitfall in using the BOA is to forget to call impl_is_ready. Until this call returns, there is no thread listening on the port from which IIOP requests are received. The remote client may hang because of this.
When impl_is_ready is called with no argument. The calling thread would be blocked indefinitely in the function until impl_shutdown (see below) is called. The thread that is calling impl_is_ready is not used by the BOA to perform its internal functions. The BOA has its own set of threads to process incoming requests and general housekeeping. Therefore, it is not necessary to have a thread blocked in the call if it can be put into use elsewhere. For example, the main thread may call impl_is_ready once in non-blocking mode (see below) and then enter the event loop to handle the GUI frontend.
If non-blocking behaviour is needed, the NonBlocking argument should be set to 1. For instance, if you creates a callback object, you might call impl_is_ready in non-blocking mode to tell the BOA to start receiving IIOP requests before sending the callback object to the remote object. The first argument ImplementationDef_ptr is ignored by the BOA. Just set the argument to nil.
impl_is_ready is thread safe and can be called multiple times. Multiple threads can be blocked in impl_is_ready.
Once the BOA is initialised, objects can be registered. The purpose of object registration is to let the BOA know of the existence of the object and to dispatch requests for the object as upcalls into the object.
To register an object, the _obj_is_ready function should be called. _obj_is_ready is a member function of the implementation skeleton class. The function should be called only once for each object. The call should be made only after the object is fully initialised.
The member function obj_is_ready of the BOA may also be used to register an object. However, this function has been superseded by _obj_is_ready and should not be used in new application code.
Once an object is registered, it is under the management of the BOA. To remove the object from the BOA and to delete it (when it is safe to do so), the _dispose function should be called. _dispose is a member function of the implementation skeleton class. The function should be called only once for each object.
Notice the asymmetry in object instantiation and destruction. To instantiate an object, the application code has to call the new operator. To remove the object, the application should never call the delete operator on the object directly.
At the time the _dispose call is made, there may be other threads invoking on the object, the BOA ensures that all these calls are completed before removing the object from its internal tables and calling the delete operator.
Internally, the BOA keeps a reference count on each object. Initially, the reference count is 0. After a call to _obj_is_ready, the reference count is 1. The BOA increases the reference count by 1 before an upcall into the object is made. The count is decreased by 1 when the upcall returns. _dispose decreases the reference count by 1, if the reference count is 0, the delete operator is called. If the count is non-zero, the object is marked as disposed. The object will be deleted when the reference count eventually goes to zero.
The reference count is also increased by 1 for each object reference held in the same address space. Hence, the delete operator will not be called when there are outstanding object references in the same address space. To ensure that an object is deleted, all its object references in the same address space should be released using CORBA::release.
Unlike colocated object references, references held by clients in other address spaces would not prevent the deletion of objects. If these clients invoke on the object after it is disposed, the system exception INV_OBJREF would be raised. The difference in semantics is an undesirable side-effect of the current BOA implementation. In future, colocated references will have the same semantics as remote references, i.e. their presence will not delay the deletion of the objects.
Instead of _dispose, it may be useful to have a method to deactivate the object but not deleting it. This feature is not supported in the current BOA implementation.
The BOA can be withdrawn from service using member functions impl_shutdown and destroy.
class BOA { public: void impl_shutdown(); void destroy(); };
impl_shutdown and destroy are the inverse of impl_is_ready and BOA_init respectively.
impl_shutdown deactivates the BOA. When the call returns, all the internal threads and network connections will be shutdown. Any thread blocking in impl_is_ready would be unblocked. After the call, no request from other address spaces will be processed. In other words, the BOA will be in the same state as it was in before impl_is_ready was called. For example, a remote client may hang if it tries to connect to the server after impl_shutdown was called because no thread is listening on the IIOP port.
impl_shutdown does not wait for incoming requests to complete before it closes the network connections. The remote clients will see the network connections shutdown and the replies may not reach them even if the upcalls have been completed. Therefore, if the application is to define an operation in an IDL interface to shutdown the BOA, the operation should be defined as an oneway operation.
impl_shutdown is thread-safe and can be called multiple times. The call is silently ignored if the BOA has already been shutdown. After impl_shutdown is called, the BOA can be reactivated by another call to impl_is_ready.
It should be noted that impl_shutdown does not affect outgoing network connections. That is, clients in the same address space will still be able to make calls to objects in other address spaces.
While remote requests are not delivered after impl_shutdown is called, the current implementation does not stop colocated clients from calling the objects. In future, colocated clients will exhibit the same behaviour as remote clients.
destroy permanently removed the BOA. This function will call impl_shutdown implicitly if it has not been called. When this call returns, the IIOP port(s) held by the BOA will be freed. Remote clients will see their requests refused by the operating system when they try to open a connection to the IIOP port(s).
After destroy is called, the BOA should not be used. If there is any objects still registered with the BOA, the objects should not be invoked afterwards. The objects are not disposed. Invoking on the objects after destroy would result in undefined behaviour. Initialisation of another BOA using BOA_init is not supported. The behaviour of BOA_init after this call is undefined.
The following member functions are not implemented. Calling these functions do not have any effect.
Since 2.5.0, there is limited support for loading objects on demand. An application can register a handler for loading objects dynamically. The handler should have the signature omniORB::loader::mapKeyToObject_t:
namespace omniORB { ... class loader { public: typedef CORBA::Object_ptr (*mapKeyToObject_t) (const objectKey& key); static void set(mapKeyToObject_t NewKeyToObject); }; };
When the ORB cannot locate the target object in this address space, it calls the handler with the object key of the target. The handler is expected to instantiate the object, either in this address space or in another address space, and returns the object reference to the newly instantiated object. The ORB will then reply with a LOCATION_FORWARD message to instruct the client to retry using the object reference returned by the handler. When the handler returns, the ORB assumes ownership of the returned value. It will call CORBA::release() on the returned value when it has finished with it.
The handler may be called concurrently by multi-threads. Hence it must be thread-safe.
If the handler cannot load the target object, it should return CORBA::Object::_nil(). The object will be treated as non-existing.
The application registers the handler with the ORB at runtime using omniORB::loader::set(). This function is not thread-safe. Calling this function again will replace the old handler with the new one.