[Up: Object Adapters]
[Previous: Basic Object Adapter] [Next: Other Object Adapters]
Compared with the ten pages of BOA specification in CORBA 2.0, the 63 pages for the Portable Object Adapter are impressive. Indeed it is not a simple replacement as a new ``basic'' object adapter, it is rather designed to be universal. Its two foremost design goals are
Each POA maintains its own Active Object Map, a table mapping the currently active objects to servants. Objects are activated within a particular POA instance, and henceforth associated with ``their'' POA, identified by a unique Object Id within its namespace.
Synchronization between POAs is achieved by POA Managers, which control the readiness of one or more POAs to receive requests. Figure 4.4 shows an example of using many POAs: the Root POA has two children, and the POA named ABC has another child, with which it shares a separate POA Manager. Servants can be registered with any of the four POAs.
Apart from having control over synchronization, the POA provides many hooks that enable a user to influence request processing:
Each POA has its own separate set of policies, which adjust different aspects of a POA's behavior. Policies are selected by the user upon POA creation and cannot be changed over its lifetime. Since objects are associated with a fixed POA instance, some policies can also be said to be that of an object's, most obvious with the lifespan policy.
The Id assignment policy can relieve the developer of the need to generate unique names. While this feature is also redundant, it is nonetheless useful, as the Object Id is not of interest in many simple servers, where automated selection of a unique Id saves a lot of unnecessary code.
The other policies are more crucial and will be referred to again in later sections.
One problem with the BOA was that no mechanism existed to synchronize servants, or to control a servant's readiness to receive requests: as soon as an object was activated, invocations were performed, and as soon as it was deactivated, a new server would be started by the BOA.
The POA provides ``POA Managers'' for this purpose. A POA Manager is a finite state machine associated with one or more POA instances that can enter one of four states, with state transitions as shown in figure 4.5.
By using one POA Manager for more than one POA, for example only one POA Manager for the complete server, it is possible to control more than one group of objects with a single call, thus working around possible race conditions if each POA had to switch state individually.
When a request is received by the ORB, it must locate an appropriate object adapter to find the responsible servant. POAs are identified by name within the namespace of their parent POA; like in a file system, the full ``path name'' is needed to locate an object's POA - obviously, it must be possible to derive this information from the object reference. One implementation option is that the request is delivered to the Root POA, which then scans the first part of the path name and then delegates the request to one of its child POAs. The request is handed down the line until the right POA is reached.
In this respect, the user can already influence the selection process whenever a necessary child POA is not found. The programmer can implement and register an Adapter Activator that will be invoked by a parent POA in order to create a non-existing child. This can happen if a server has been, to save resources, partially deactivated by stopping part of its implementation, or if the server has been restarted and now reinitializes object adapters and servants on demand.
Creating child POAs cannot happen without user intervention, because the programmer usually wishes to assign a custom set of policies: if a new POA has to be created but no adapter activator has been registered with its parent, requests fail.
Within the adapter activator, the user cannot only create further child POAs, but also activate objects within, possibly by reading state information from disk. This process is transparent to the client, which does not notice any of this server-side activity.
Once the object's POA has been found, further processing depends on the POA's servant retention policy and its request processing policy. The POA uses the Object Id - which must again be part of the object reference - to locate the servant.
Figure 4.6 shows the three possibilities that exist to find the responsible servant. In the most simple case, servants are activated explicitly and entered into the Active Object Map. If a POA has a servant retention policy of ``retain,'' this table is consulted first. If an appropriate entry is found, the request is handed to the registered servant.
If the servant retention policy is ``non-retain,'' or no matching entry is found in the Active Object Map, the request processing policy is considered. If its value is ``use default servant,'' the user can provide a single default servant that will be used regardless of the request's Object Id. This allows an implementation to handle a group of - usually identical - objects with a single servant. This default servant can use the POA's context-sensitive introspection methods to query the Object Id of the current request and behave accordingly.
Default servants provide scalability using the flyweight pattern : the server does not grow with the number of objects. Rather, the server can produce arbitrary numbers of object references while the number of active servants is constant.
A database server is an example for the usage of a default servant. Each table would be represented using a different POA with the table's same name, and the table's key value is used as Object Id. This way, all table rows are objects with their own object reference. Only a single default servant is needed per table; in an invocation, this default servant would query the request's Object Id and use it as table index. By using the DSI, the default servant could even be identical for all database tables, examining the table structure to select its parameter's types.
Even more flexibility, albeit of a different kind, is possible if the request processing policy is set to ``use servant manager.'' If the POA's own search for a servant in the Active Object Map is unsuccessful, it then delegates the task of locating a servant to the user-provided servant manager. The servant manager can then use intelligence of its own to find or activate a servant.
Like adapter activators for POAs, a servant manager can be used to activate servants on demand after a partial shutdown or after server restart. The servant manager receives the request's Object Id and could use that information to read back the object's state from persistent storage.
Another interesting feature possible with servant managers are virtual objects, i.e. of object references that refer to non-exisent servants. References to virtual objects can be passed to a client; on the server side, a servant manager is then registered with the POA to incarnate the virtual object on demand.
An example for this mechanism is a file service. A ``Directory'' object might provide a method to return the directory's contents - a list of files - as a sequence of object references to ``file'' objects. It would be inefficient to call all file objects into existence just for the purpose of returning their references, as only a small part of them would be actually read. Instead, the Directory would create virtual object references, encoding the file name in the Object Id. Once a file is opened, the file object would be activated by the servant manager.
Servant managers come in two different flavors with slightly different behavior and terminology, depending on the servant retention policy. If this policy's value is ``retain,'' a servant activator is asked to incarnate a new servant which will, after the invocation, be entered into the Active Object Map itself - this would be sensible in the sketched file service example, since the newly incarnated file object would be needed more than once. If an object is deactivated, either explicitly or because of server shutdown, the servant activator's etherealize method is called to get rid of the servant - at which point the object's state could be written to persistent storage.
If the servant retention policy is ``non-retain,'' the servant manager would have to be a servant locator, tasked to locate a servant suitable only for a single invocation. A servant locator supplements the default servant mechanism in providing a pool of default servants; it is the flyweight factory according to the flyweight pattern. It can also be used for load balancing, as the example of a print service shows, in which the print method is directed to the printer with the shortest queue.
Both kinds of a servant manager can also throw a special ``forward exception'' instead of returning a servant. This exception contains a new object reference to forward the request to, possibly to an object realized in a different server on another system, employing the GIOP location forwarding mechanism. Forwarding allows, for example, the implementation of load balancing or redundant services: the servant manager would check its replicated servers and forward the request to an available one.
Another idea, already realized in the K Office Suite, is that of a ``Meta Service,'' in which a servant activator checks the request's Object Id, launches the appropriate server, for example a Web browser or mail tool, and then forwards the request to the new server .
The flowchart in figure 4.7 shows in full how a POA tries to locate a servant according to its policies. To summarize a developer's choices, table 4.1 shows a matrix of the different types of servants and their possible design patterns:
Figure 4.3 showed the lifecycle of objects that used the Basic Object Adapter. The POA adds object virtuality as a fourth state. A virtual object is not currently associated with a servant; virtual objects are usually monitored by a servant activator so that they can be activated on demand. The transition from the virtual to the active state is called incarnation, and the transition from the active to the virtual state etherealization, corresponding to the methods called in the servant activator. An object is inactive if its implementation is not running.
The lifecycle of POA-based objects also depends on the POA's lifespan policy, which can take the values ``transient'' or ``persistent.'' The lifetime of a transient object - an object activated in a POA with the transient lifespan policy - is bounded by the lifetime of its POA instance: once it is destroyed, all object references pointing to that POA's transient objects become invalid. It must not be possible to either intentionally or unintentionally reactivate a transient object when the same or another server is started again. The lifecycle of a transient object is shown in figure 4.8. Transient objects cannot become inactive, because if their server is shut down, they cease to exist and can never become active again.
On the other hand, persistent objects can, according to definition, outlive their server. It must be possible to start a server and activate objects that respond to object references exported from an old server. Their lifecycle is shown in figure 4.9 and differs from the lifecycle of a transient object in that a persistent object never fully ceases to exist. If their server is shut down, persistent objects become inactive, and can be activated again in a new server.
It is certainly possible that an inactive object is not associated with an implementation, or that a new server does not reactivate an object. In both cases, clients attempting an invocation will receive an exception and will probably consider the object non-existent, but other clients do not notice, and if the object is reactivated at any point in time by a later servant, those clients can again perform method invocations.
Persistent objects have several advantages over transient objects, as it is possible
The BOA, where all objects satisfied the POA's definition of persistence, provided the Implementation Repository for the purpose of on-demand server restart. The POA specification does not address this issue and deems it to be the vendor's problem. From the description of the persistent lifespan policy:
``Administrative action beyond the scope of this specification may be necessary to inform the ORB's location service of the creation and eventual termination of existence of this POA, and optionally to arrange for on-demand activation of a process implementing this POA.''An external POA component, like a daemon, is useful for the same reason as with the BOA, to receive requests while the actual server is not running, to start a new server if necessary or simply to wait until a new server is started manually, and then to forward the request. Without such a service, requests are failed with an exception indicating temporary failure while the server is down for replacement.
Since its interface is specified in IDL, most of the POA's mapping into a programming language is automatic. Left open, though, is the servant type, which in IDL is defined as ``native'' and must therefore be mapped specifically by each language mapping.
Currently, the two most widely used languages used for CORBA programming are C++ and Java. This thesis centers on C++ for other reasons, yet a glance into the CORBA 2.2 Java language mapping  shows that the POA's servant type is not defined there, indeed, that Java's server-side mapping is very minimalistic and does not even provide any kind of object adapter.4.3 By containing a comment about waiting for the availability of the POA specification, the Java language mapping appears rushed and out of date.4.4 This will be fixed in CORBA 2.3 .
The C++ mapping does already contain the necessary mapping rules and definitions, including two different approaches how servants can be implemented, by inheritance or by delegation, according to the two forms of the Adapter pattern .
A servant is defined as an instance of a C++ class that, directly or indirectly, inherits from the system class PortableServer::Servant. Using inheritance, the IDL compiler generates a skeleton class with the same name as the IDL interface prefixed by ``POA_.'' The user derives from the skeleton and implements the operations and attributes. With delegation, the IDL compiler generates a ``tie'' template; the user then provides a C++ class that implements all operations and attributes, but does not need to inherit any CORBA classes - a questionable feature, supposedly for easing the migration of existing code into a CORBA server, where the source code for existing libraries is not available and their inheritance therefore unchangeable. However, in that case tie classes aren't of much help, too, since they require the signature of existing code to match the C++ language mapping, or manual template specialization.
Examples for the implementation of servants using inheritance or delegation are shown in . The text describes the same problems with delegation and also cannot present reasonable motivation for delegation either.
Still, some minor complaints remain. One is an omission with respect to implementing derived interfaces. If an IDL interface Derived inherits interface Base, the inheritance of the generated skeletons is undefined. Figure 4.10 shows two possible inheritance graphs: on the left, Derived's skeleton inherits Base's skeleton, while it can also be directly derived from the system base class, as shown on the right side. The important difference to the user is that the first possibility on the left allows for implementation inheritance by deriving the implementation of Derived from both the skeleton and the implementation of Base, while the other requires the user to re-implement Base's operations in the implementation of Derived. Clearly, the first possibility is more sensible, but no word is spent on the subject, so a CORBA-compliant ORB implementation could choose the ``wrong'' inheritance on the right.
Another uncomfortable issue is that of reference counting. Obviously, a servant must live as long as it is active and therefore still referenced in a POA's Active Object Map. Frequently, however, the user does not want to keep track of a servant and delete it manually after its deactivation. If a servant manager is not used, it would be more comfortable to have a reference counting mechanism that deleted servants when the last reference is lost. After a servant's activation, the user could then release the reference immediately and forget about it.
In a multithreaded environment, reference counting is essential, because many invocations can be active at a given time, and a servant must not be deleted while any of them is in progress.
Reference counting for servants did not exist in CORBA 2.2, but will be added to the C++ language mapping in 2.3 . The OMG decided on a backwards-compatible solution using a ``mix-in'' class. Without special effort, servants are not reference-counted, resulting in the same behavior as in 2.2. To employ reference counting, the user derives a servant class from PortableServer::RefCountServantBase.
While being a nice recovery, it is surprising why this mechanism was not already used in CORBA 2.2. To achieve backwards-compatibility, reference counting is now much more complicated as it could have been. And, since it depends on inheritance, it only works with the inheritance-based approach, not with delegation.
The most striking difference between the BOA and the POA is the information they manage. While the BOA dealt with servers and completely ignored the subject of servants, this approach is turned upside-down in the POA, which deals with servants but completely ignores the handling of servers and processes.
However, the POA does solve its primary task, of adapting servants to the ORB and managing them, very effectively. The complaints with the BOA described in section 4.1.1 are solved: the POA provides a clear definition of the term ``servant,'' their activation and deactivation, and the layout of skeleton classes. In this respect, the specification is complete; no incompatibilities need to be introduced by vendor's interpretations. Table 4.2 compares the features of BOA and POA.
Default servants provide scalability, and adapter activators and servant managers allow for much flexibility. Using these hooks, user code can be involved at many steps during request processing. For example, servant managers can be used to realize persistency by saving and restoring an object's state. POA Managers provide the required means of synchronization with the ORB.
All in all, the POA indeed achieves its goal. Not so much as being ``portable,'' though. The unportability of BOA-based server code was only a secondary result of its inadequate specification. Rather, quoting the Portability Enhancement RFP, the POA succeeds as a universal object adapter.
And yet an incompleteness remains with the persistent lifespan policy. Resulting from the design decision not to cover the uncomfortable area of server handling, the automated restart of servers is not addressed as it was with the BOA's Implementation Repository. While transient objects can be implemented in a portable manner, incompatibilities may arise again with persistent objects. This would be particularly annoying, since it is to be expected that many ``real life'' servers will require persistence.
The next chapter will show that persistence not necessarily introduces source-level incompatibilities, but TAO, for example, intends to extend the POA interface with methods for registering objects with an implementation repository.4.5
Frequently brought forward as a pro-POA argument is that it supports multithreading. It is true that the POA specification addresses the issue, but the threading policy is coarse and insufficient for many multithreading requirements. It does not allow user control over threading issues but simply selects whether servants are or are not thread safe, either serializing requests or dispatching them as controlled by the ORB on a per-POA basis. Ideally, a ``thread manager'' would be registered with a POA to assign requests to threads, either by selecting from a thread pool or by starting new threads.
A small inconvenience from a technical rather than conceptual nature is the implementation of adapter activators and servant managers. According to the specification, they follow the same semantics as normal servants. As such, they must be activated, and method invocations are subject to their POA's readiness. Apart from allowing embarrassing situations like the need for invoking an adapter activator and a servant manager in order to incarnate another adapter activator, this can cause problems if only one POA Manager is used: if the POA Manager is set to the inactive state prior to server shutdown, objects could not be etherealized, since the servant manager, too, cannot receive requests any more. While this effect is surprising,4.6 it is not a bug and can be avoided by the careful programmer.
Handling adapter activators and servant managers as normal objects seems natural, as their interface is specified in IDL. But both take ``native'' parameters, so automated stub and skeleton code generation does not work anyway. It would have been less troublesome, if less consequent, to declare adapter activators and servant managers as ``pseudo objects,'' like the POA itself.
A few open issues against the POA remain on the OMG Web site [33,34], but apart from those mentioned here, they are of a minor technical nature, for example whether the special case of nested method invocations on the same object would violate the single-thread threading policy.
Of course, the POA is bound by the current limitations of CORBA itself (see section 3.5). It supports request-response communication, but cannot adapt data streams, and does not provide real-time features.
The mentioned quirks in the C++ mapping and the remaining open issues will hopefully be resolved in a future version of the CORBA standard.
[Previous: Basic Object Adapter] [Next: Other Object Adapters]
[Up: Object Adapters]