When a client acquires a reference to an object in another address space, omniORB2 creates a local representation of the object and returns a pointer to this object as its object reference. The local representation is known as the proxy object.
The proxy object maps each IDL operation into a method to deliver invocations to the remote object. The method implements argument marshalling using the ORB runtime. When the ORB runtime detects an error condition, it may raise a system exception. These exceptions will normally be propagated by the proxy object to the application code. However, there may be applications that prefer to have the system exceptions trapped in the proxy object. For these applications, it is possible to install exception handlers for individual proxy object or all proxy objects. The API to do this will be explained in this chapter.
As described in section 6.2, proxy objects are created by instances of the proxyObjectFactory class. For each IDL interface A, the stubs of A contains a derived class of proxyObjectFactory (A_proxyObjectFactory). This derived class is responsible for creating proxy objects for A. This process is completely transparent to the application. However, there may be applications that require greater control on the creation of proxy objects or even want to change the behavior of the proxy objects. To cater for this requirement, applications can override the default proxyObjectFactories and install their own versions of proxyObjectFactories. The way to do this will be explained in this chapter.
By default, all system exceptions, with the exception of CORBA::TRANSIENT, are propagated by the proxy objects to the application code. Some applications may prefer to trap these exceptions within the proxy objects so that the application logic does not have to deal with the error condition. For example, when a CORBA::COMM_FAILURE is received, an application may just want to retry the invocation until it finally succeeds. This approach is useful for objects that are persistent and their operations are idempotent.
OmniORB2 provides a set of functions to install exception handlers. Once they are installed, proxy objects will call these handlers when the target system exceptions are raised by the ORB runtime. Exception handlers can be installed for CORBA::TRANSIENT, CORBA::COMM_FAILURE and CORBA::SystemException. The last handler covers all system exceptions other than the two covered by the first two handlers. An exception handler can be installed for individual proxy object or it can be installed for all proxy objects in the address space.
When a CORBA::TRANSIENT exception is raised by the ORB runtime, the default behaviour of the proxy objects is to retry indefinitely until the operation succeeds. Successive retries will be delayed progressively by multiples of omniORB::defaultTransientRetryDelayIncrement. The delay will be limited to a maximum specified by omniORB::defaultTransientRetryDelayMaximum. The unit of both values are in seconds.
The ORB runtime will raised CORBA::TRANSIENT under the following conditions:
Applications can override the default behaviour by installing their own exception handler. The API to do so is summarised below:
class omniORB { public: typedef CORBA::Boolean (*transientExceptionHandler_t)(void* cookie, CORBA::ULong n_retries, const CORBA::TRANSIENT& ex); static void installTransientExceptionHandler(void* cookie, transientExceptionHandler_t fn); static void installTransientExceptionHandler(CORBA::Object_ptr obj, void* cookie, transientExceptionHandler_t fn); static CORBA::ULong defaultTransientRetryDelayIncrement; static CORBA::ULong defaultTransientRetryDelayMaximum; }
The overloaded functions installTransientExceptionHandler can be used to install the exception handlers for CORBA::TRANSIENT.
Two overloaded forms are available. The first form install an exception handler for all object references except for those which have an exception handler installed by the second form, which takes an addition argument to identify the target object reference. The argument cookie is an opaque pointer which will be passed on by the ORB when it calls the exception handler.
An exception handler will be called by proxy objects with three arguments. The cookie is the opaque pointer registered by installTransientExceptionHandler. The argument n_retries is the number of times the proxy has called this handler for the same invocation. The argument ex is the value of the exception caught. The exception handler is expected to do whatever is appropriate and returns a boolean value. If the return value is TRUE(1), the proxy object would retry the operation again. If the return value is FALSE(0), the CORBA::TRANSIENT exception would be propagated into the application code.
The following sample code installs a simple exception handler for all objects and for a specific object:
CORBA::Boolean my_transient_handler1 (void* cookie, CORBA::ULong retries, const CORBA::TRANSIENT& ex) { cerr << ''transient handler 1 called.'' << endl; return 1; // retry immediately. } CORBA::Boolean my_transient_handler2 (void* cookie, CORBA::ULong retries, const CORBA::TRANSIENT& ex) { cerr << ''transient handler 2 called.'' << endl; return 1; // retry immediately. } static Echo_ptr myobj; void installhandlers() { omniORB::installTransientExceptionHandler(0,my_transient_handler1); // All proxy objects will call my_transient_handler1 from now on. omniORB::installTransientExceptionHandler(myobj,0,my_transient_handler2); // The proxy object of myobj will call my_transient_handler2 from now on. }
When the ORB runtime fails to establish a network connection to the remote object and none of the conditions listed above for raising a CORBA::TRANSIENT is applicable, it raises a CORBA::COMM_FAILURE exception.
The default behaviour of the proxy objects is to propagate this exception to the application.
Applications can override the default behaviour by installing their own exception handlers. The API to do so is summarised below:
class omniORB { public: typedef CORBA::Boolean (*commFailureExceptionHandler_t)(void* cookie, CORBA::ULong n_retries, const CORBA::COMM_FAILURE& ex); static void installCommFailureExceptionHandler(void* cookie, commFailureExceptionHandler_t fn); static void installCommFailureExceptionHandler(CORBA::Object_ptr obj, void* cookie, commFailureExceptionHandler_t fn); }
The functions are equivalent to their counterparts for CORBA::TRANSIENT.
To report an error condition, the ORB runtime may raise other SystemExceptions. If the exception is neither CORBA::TRANISENT nor CORBA::COMM_FAILURE, the default behaviour of the proxy objects is to propagate this exception to the application.
Application can override the default behaviour by installing their own exception handlers. The API to do so is summarised below:
class omniORB { public: typedef CORBA::Boolean (*systemExceptionHandler_t)(void* cookie, CORBA::ULong n_retries, const CORBA::SystemException& ex); static void installSystemExceptionHandler(void* cookie, systemExceptionHandler_t fn); static void installSystemExceptionHandler(CORBA::Object_ptr obj, void* cookie, systemExceptionHandler_t fn); }
The functions are equivalent to their counterparts for CORBA::TRANSIENT.
This section describes how an application can control the creation or change the behaviour of proxy objects.
For each interface A, its stub contains a proxy factory class- A_proxyObjectFactory. This class is derived from CORBA::proxyObjectFactory and implements three virtual functions:
class A_proxyObjectFactory : public virtual CORBA::proxyObjectFactory { public: virtual const char *irRepoId() const; virtual _CORBA_Boolean is_a(const char *base_repoId) const; virtual CORBA::Object_ptr newProxyObject(Rope *r, CORBA::Octet *key, size_t keysize, IOP::TaggedProfileList *profiles, CORBA::Boolean release); };
As described in chapter 6, the functions allow the ORB runtime to perform type checking. The function newProxyObject creates a proxy object for A based on its input arguments. The return value is a pointer to the class _proxy_A which is automatically re-casted into a CORBA::Object_ptr. _proxy_A implements the proxy object for A:
class _proxy_A : public virtual A { public: _proxy_A (Rope *r, CORBA::Octet *key, size_t keysize,IOP::TaggedProfileList *profiles, CORBA::Boolean release); virtual ~_proxy_A(); // plus other internal functions. };
The stub of A guarantees that exactly one instance of A_proxyObjectFactory is instantiated when an application is executed. The constructor of A_proxyObjectFactory, via its base class proxyObjectFactory links the instance into the ORB's proxy factory list.
Newly instantiated proxy object factories are always entered at the front of the ORB's proxy factory list. Moreover, when the ORB searches for a match on the type, it always stops at the first match. In other words, when additional instances of A_proxyObjectFactory or derived classes of it are created, the last instantiation will override earlier instantiations to be the proxy factory selected to create proxy objects of A. This property can be used by an application to install its own proxy object factories.
Using the Echo example in chapter 2 as the basis, one can tell the ORB to use a modified proxy object class to create proxy objects. The steps involved are as follows:
We define a new proxy class to cache the result of the last invocation of echoString.
class _new_proxy_Echo : public virtual _proxy_Echo { public: _new_proxy_Echo (Rope *r, CORBA::Octet *key, size_t keysize,IOP::TaggedProfileList *profiles, CORBA::Boolean release) : _proxy_Echo(r,key,keysize,profiles,release), omniObject(Echo_IntfRepoID,r,key,keysize,profiles,release) { // You have to look at the _proxy_Echo class and copy from its // ctor all the explicit ctor calls to its base member. } virtual ~_new_proxy_Echo() {} virtual char* echoString(const char* mesg) { // // Only calls the remote object if the argument is different from the // last invocation. omni_mutex_lock sync(lock); if ((char*)last_arg) { if (strcmp(mesg,(char*)last_arg) == 0) { return CORBA::string_dup(last_result); } } char* res = _proxy_Echo::echoString(mesg); last_arg = mesg; last_result = (const char*) res; return res; } private: omni_mutex lock; CORBA::String_var last_arg; CORBA::String_var last_result; };
Next, we define a new proxy factory class to instantiate _new_proxy_Echo as proxy objects for Echo.
class _new_Echo_proxyObjectFactory : public virtual Echo_proxyObjectFactory { public: _new_Echo_proxyObjectFactory () {} virtual ~_new_Echo_proxyObjectFactory() {} // Only have to override newProxyObject virtual CORBA::Object_ptr newProxyObject(Rope *r, CORBA::Octet *key, size_t keysize, IOP::TaggedProfileList *profiles, CORBA::Boolean release) { _new_proxy_Echo *p = new _new_proxy_Echo(r,key,keysize,profiles,release); return p; } };
Finally, we have to instantiate a single instance of the new proxy factory in the application code.
int main(int argc, char** argv) { // Other initialisation steps _new_Echo_proxyObjectFactory* f = new _new_Echo_proxyObjectFactory; // Use the new operator to instantiate the proxy factory and never // call the delete operator on this instance. // From this point onwards, _new_proxy_Echo will be used to create // proxy objects for Echo. }
Notice that the ORB may call newProxyObject multiple times to create proxy objects for the same remote object. In other words, the ORB does not guarantee that only one proxy object is created for each remote object. For applications that require this guarantee, it is necessary to check within newProxyObject whether a proxy object has already been created for the current request. If the argument Rope* r points to the same structure and the content of the sequence CORBA::Octet* key is the same, then an existing proxy object can be returned to satisfy the current request. Do not forget to call CORBA::duplicate() before returning the object reference.
newProxyObject may be called concurrently by different threads within the ORB. Needless to say, the function must be thread-safe.