Previous Up Next

Chapter 5  Concepts for Server-side Programming

5.1  Object

A company is just a concept (with supporting, legal documentation) rather than a physical entity. For example, a building owned by the company is not the company. Likewise, an employee is not the company (but he or she can represent the company). Just as a company is a concept rather than a physical entity, so too, a CORBA object is just a concept, rather than a physical entity.

5.2  Servant

An employee represents a company. For example, let us assume that a utility company called EasyGas supplies gas to your home. You might say to a friend, “I talked to EasyGas yesterday to change my payment plan.” Technically, you did not talk to EasyGas because EasyGas is a company—a legal concept—and it is impossible to talk to a concept. What you actually did was talk to an employee (a representative) of EasyGas. In the same way, a programmer may say, “This CORBA client invokes upon a CORBA object in the billing server.” Technically, a CORBA object is just a concept so there is no way to invoke an operation upon it. Instead, the invocation is handled by a servant that represents the CORBA object.

A servant is simply a data-structure or object in the programming language that is used to implement a server application. For example, a servant might be a C++ object or a Java object.

5.3  Why are Object and Servant Different Concepts?

An obvious question is why does the CORBA specification make a distinction between a CORBA object and a servant (C++/Java object) that represents it? The answer can be explained by the following analogy with a company and its employees.

The lifetime of a company is independent of the lifetime of an individual employee. For example, when you telephoned EasyGas yesterday, you might have spoken to an employee called John. If John leaves EasyGas then when you telephone EasyGas again tomorrow, you might talk to another employee, say, Mary. In the same way, when a CORBA client makes an invocation upon a CORBA object, the request might be handled by a particular servant (C++ or Java object). If the server process is later killed then obviously the servant (C++ or Java object) will be destroyed. Then, if the server is restarted, a new servant will be created to represent the same CORBA object. What all this means is that a future invocation by the client application upon the same CORBA object may be handled by a different servant. In turn, this means that a CORBA object can be “long lived”, that is, it can survive a stop-and-restart of a server process (just as a company can survive the resignation of one employee and the hiring of a new, replacement employee).

5.4  Object Adapters

An object adapter (OA) is the part of the CORBA runtime system that “adapts” the concepts of a CORBA object to the realities of the host programming language and server process. In practice, this means that the OA deals with low-level issues, such as:

The CORBA specification is deliberately vague about the capabilities of an object adapter. This is because the OMG felt that it would not be feasible to have a one-size-fits-all object adapter. Instead, the OMG expected that several different object adapters would be developed. The first version of the CORBA specification defined a Basic Object Adapter (BOA) and suggested that other object adapters—perhaps a Database Object Adapter or a Real-time Object Adapter—could be developed. Unfortunately, this goal was not achieved. The reason for this is that the BOA was under-specified so CORBA vendors had to add their own proprietary enhancements just to get a working system. Having done this, CORBA vendors decided to not define new object adapters for, say, database integration or to support real-time programming. Instead, they just added more and more proprietary APIs to the BOA to provide the desired functionality. The result was that each CORBA vendor had their own proprietary version of the BOA, and this hindered source-code portability of applications across different CORBA products.

After a few years, the OMG decided to discard the BOA and to replace it with the Portable Object Adapter (POA). The POA is superior to the BOA in two ways:

  1. The POA has much more built-in functionality than the BOA. This dramatically reduces the need for CORBA vendors to add their own proprietary enhancements. It also reduces the need for programmers to use proprietary enhancements and hence increases source-code portability of applications across different CORBA implementations.
  2. The architecture of the POA provides an open-ended way for new functionality to be added in the future. This provides a way for the OMG to incrementally built upon the capabilities of the POA, in a backwards-compatible way. It also allows CORBA vendors to provide proprietary enhancements in a way that retains the “look and feel” of existing POA-based APIs.

5.5  Portable Object Adapter (POA)

A POA is a collection of servants. This is a collection in the same sense that arrays, linked lists, hash tables and sets are collections. A POA is created with certain policy (quality of service) values. The policy values associated with a POA are applied to the servants contained within the POA. For example, a POA might be multi-threaded or single-threaded.1 If the POA is multi-threaded then it can dispatch requests concurrently to the servants contained inside it. Conversely, if a POA is single-threaded then it ensures that the dispatching of requests is serialized for the servants that it contains.

A server can have several POAs. Because each POA can have different policy values—and the policy values are applied to the servants contained within a POA—this means that you can apply different policy values to different (collections of) servants. For example, if some servants are implemented in a thread-safe manner then you could put them into a multi-threaded POA. Conversely, if some other servants are not thread-safe then you could put them into a single-threaded POA.

The CORBA specification does not place any restrictions on how many POAs you may have in an application, or on how many servants may be within one POA. For example, at one extreme, you could place all your servants into the same POA. At the other extreme, you could create a separate POA for each servant. A good rule of thumb is to have a separate POA for each IDL interface. This gives you the flexibility of choosing different policy values for different IDL interfaces. Each POA can be given an arbitrary name. If you have a separate POA for each IDL interface then a good convention is to use the name of an IDL interface as the name of its associated POA.

5.5.1  POA Hierarchy

Within any type of hierarchical diagram, the lines connecting the nodes in the hierarchy indicate a relationship. For example, in an organizational hierarchy the lines indicate a reports to relationship, while in an object-orientation type hierarchy the lines indicate an is a relationship.

Figure 5.1: Example POA hierarchy

All the POAs in a CORBA server are arranged in a hierarchy. The lines within a POA hierarchy indicate an order of destruction relationship. For example, consider the POA hierarchy in Figure 5.1. The CORBA runtime system provides a built-in POA called the root POA.2 As its name suggests, this node forms the root of the POA hierarchy. The order of destruction guarantee means that, at server shutdown time, the POAs will be destroyed in the following order:

No order of destruction guarantees are given for sibling nodes. For example, the relative order of destruction of the Foo and Bar POAs is not guaranteed. Neither is the relative order of destruction guaranteed for the FooFactory, BarFactory or Administration POAs.

The reason why the order of destruction is useful is that the programmer can optionally arrange for the servants within a POA to be destroyed when the containing POA is destroyed.3 In essence, a programmer can use the order of destruction of the POA hierarchy to impose an order of destruction on servants.

POA hierarchies tend to be very shallow. There are two reasons for this. First, most CORBA servers tend to implement a relatively small number of IDL interfaces (typically less than 5). Since it is natural to have one POA for each IDL interface, this means that there will be only a few POAs within a server process. Second, CORBA server applications tend to have only simple order-of-destruction requirements, and this is reflected in a very flat hierarchical structure.

5.6  Different Kinds of POA

Section 5.5 mentioned that a POA is a collection of servants. In fact, a POA can be one of several kinds of collection. This allows programmers to make various tradeoffs regarding the lifecycle of servants and the relationship between servants and the CORBA objects that they represent. For example:

The possibilities listed above allow programmers to choose different techniques in order to meet a wide variety of performance and scalability requirements.

Figure 5.2: Possible components of a POA

In order to provide these possibilities, a POA must be a flexible type of container. Figure 5.2 shows the logical structure of a POA. This figure illustrates that a POA may have: a default servant, one of two subtypes of servant manager (either a ServantActivator or a ServantLocator), and/or an active object map (AOM). The meaning of these components will be made clear later. It is impossible for a POA to have all of these components at the same time. Instead, a POA has at most two of them. Which components a POA has determines the performance and scalability characteristics of the POA. The following subsections discuss four common “kinds” of POA, where each kind makes use of a certain combination of the components.

5.6.1  POA kind 1: “Simple”

The simplest kind of POA is one that has just an active object map. This is illustrated in Figure 5.3. The term active object map (AOM) is not very intuitive so it worthwhile explaining it. The term map means a lookup table that “maps” a name to a value. The term active object means a CORBA object that is “active”, that is, currently has a servant associated with it.

Figure 5.3: A “simple” POA

An IOR (Chapter 10) contains the “contact details” that a client application uses to communicate with a CORBA object. These contact details include the host and port of the server process, and also an object key that uniquely identifies the target object within the server process—the object key information is required because there may be many objects within the same server process. The format of an object key varies from one CORBA product to another, but it typically contains the following information:

When a request arrives at a server, the header of the request contains the object key for the target object. The CORBA runtime system in the server extracts the POA name and the object id information from the object key. The CORBA runtime system uses this object id to perform a lookup in the AOM of the specified POA. If there is a servant associated with this object id then the request is dispatched to that servant. Otherwise, an OBJECT_NOT_EXIST exception is thrown back to the client.

You can use a “simple” POA under the following conditions:

5.6.2  POA kind 2: “Lazy Loader”

In this POA model (Figure 5.4), the server programmer creates the POA and then associates it with an object that implements the ServantActivator interface. The name ServantActivator is not very intuitive; perhaps lazy loader would be a better name because its purpose is to lazily load (or “re-create on demand”) servants. It is the programmer’s responsibility to implement the lazy loader class.

Figure 5.4: A “lazy loader” POA

When a request arrives at a server, the CORBA runtime extracts the POA name and the object id information from the object key. The CORBA runtime system uses this object id to perform a lookup in the AOM of the specified POA. If there is a servant associated with this object id then the request is dispatched to that servant. Otherwise, the POA invokes the incarnate() operation on the lazy loader to ask it to re-create the desired servant, and the POA then adds this servant into its AOM. If the lazy loader is unable or unwilling to re-create the servant then an OBJECT_NOT_EXIST exception is thrown back to the client.

You can use a “lazy loader” POA under the following conditions:

5.6.3  POA kind 3: “Cache”

In this POA model (Figure 5.5), the server programmer creates the POA and then associates it with an object that implements the ServantLocator interface. The name ServantLocator is not very intuitive; cache would be a better name because its purpose is to implement a cache of servants. It is the programmer’s responsibility to implement the cache class.

Figure 5.5: A “cache” POA

When a request arrives at a server, the CORBA runtime passes control to the POA specified in the object key in the header of the request. The POA then uses the following (pseudocode) algorithm to dispatch the request:


sv = cache.preinvoke(object_id,…);
sv.operation(…);
cache.postinvoke(…, sv, …);

The POA does not maintain its own AOM. This is because it assumes that the cache will implement its own AOM-like data-structure and there would be no point in the POA replicating this effort.

You can use a “cache” POA if you have enough memory to store some but not all of the servants in memory at the same time. Typically, the CORBA server will be front-ending a database. To optimize access to information in the database, you decide to cache some of that database information in servants (memory). Of course, caching information in memory has associated dangers. In particular, if you update the cached/in-memory information and do not immediately flush this information to the database then you run two risks. First, if another application accesses the database directly then it will see information that is different to what is in the servants. In other words, you can have a cache inconsistency problem. Second, if your CORBA server is killed before it has a chance to flush its cached information back to the database then you will lose some data. However, the “cache” POA model works very well with data in a read-only (reference) database.

5.6.4  POA kind 4: “Default Servant”

In this POA model (Figure 5.6), the server programmer creates the POA and then associates it with just one servant. This servant is called a default servant. Whenever a request arrives for any CORBA object in that POA, the POA dispatches the request to the default servant. The default servant calls the get_object_id() operation on the POACurrent (Chapter 13) to find out which CORBA object it is representing for the current request. Typically, the servant will use this object identifier as, say, the primary key into a database table.

Figure 5.6: A “default servant” POA

The “default servant” POA model minimizes memory consumption because a single servant can be used to service requests for (literally) an infinite number of CORBA objects.5 This minimal memory consumption comes at the price of a time overhead because the default servant must use the object identifier (obtained from the POACurrent) to access persistent data every time an operation is invoked. In other words, a default servant does not normally cache any data in memory for faster access.

5.6.5  Other POA kinds

The four kinds of POA discussed in Sections 5.6.1 to 5.6.4 are the most common and useful POA models. You create a specific kind of POA by specifying a corresponding combination of POA policies as a parameter to the create_POA() operation.6 There are other legal combinations of POA policies that can result in other kinds of POA. However, these other kinds of POA are not always useful. For example, a “default servant” POA may also have an active object map. When a request arrives at such a POA, the following happens:

It is difficult to think of a non-contrived use for such a POA. Instead, it is usually clearer to split this POA into two POAs: a “default servant” POA (without an activate object map) and a separate “simple” POA.

5.7  POA Managers

A water tap (or faucet as it is called in some countries) is used for turning on and off the flow of water. Conceptually, it has two states: “on” allows water to flow, and “off” prevents water from flowing.

A POA manager is similar to a water tap except that, instead of controlling the flow of water, it controls the flow of incoming requests. A POA manager can be in one of four states:

HOLDING
This is an “off” state. Incoming requests are queued up.
DISCARDING
This an an “off” state. Each incoming request is discarded and a CORBA::TRANSIENT system exception is thrown back to the client.7
ACTIVE
This is the “on” state. Incoming requests are dispatched normally to servants.
INACTIVE
This an an “off” state. This state is entered automatically when the server is shutting down. Incoming requests are rejected in a vendor-specific manner.

The name POA manager is somewhat undescriptive. A better name might have been POA request valve.

Figure 5.7: Example of POA managers

There is a one-to-many relationship between POA managers and POAs: one POA manager controls the dispatching of requests for all servants in many POAs. It is a good idea for a server application to have two POA managers, as shown in Figure 5.7. One POA manager controls the dispatching of requests for servants in an “administration” POA and another POA manager controls the dispatching of requests for servants in the “core functionality” POAs. In this way, the server can selectively enable/disable its core functionality while always servicing “administration” requests.


module PortableServer { … local interface POAManager { enum State {HOLDING, ACTIVE, DISCARDING, INACTIVE}; State get_state(); void activate() raises(...); void hold_requests(...) raises(...); void discard_requests(...) raises(...); void deactivate(...) raises(...); }; };
Figure 5.8: API for a POA manager

The API for POA managers is defined in IDL, as shown in Figure 5.8. You can query the current state of a POA manager by calling get_state(). You can switch a POA manager into another state by calling activate(), hold_requests(), discard_requests() or deactivate(). Initially, a POA manager is in the HOLDING state. This is useful because it prevents incoming requests from being dispatched while a server is performing its application-level initialization. Once initialization is complete, a server program should call activate() on all its POA managers and then go into an event loop (typically by calling run() on the ORB).


1
Saying that a POA is multi-threaded or single-threaded is a slight simplification. This issue is discussed in more detail in Chapter 6.
2
The root POA is accessed by calling resolve_initial_references("RootPOA") on the ORB and then “narrowing” down to the appropriate type (PortableServer::POA). The resolve_initial_references() operation is discussed in Section 3.4.1.
3
In some programming languages, for example, C++, servants are reference counted. In such languages, the programmer can arrange for POAs to hold the only references to servants. Then as each POA in the POA hierarchy is being destroyed, the POA decrements (to zero) the reference count of all of its servants. This has the effect of destroying the servants at the same time as the POA in which they are contained. An alternative technique—and one which works regardless of whether or not servants are reference counted—is to use the “lazy loader” POA model (discussed in Section 5.6.2). When such a POA is being destroyed, it calls the etherealize() operation on its ServantActivator (lazy loader) for each servant. The programmer can implement etherealize() so it performs destruction-time behavior for servants.
4
The object id is a sequence<octet>. An octet is the built-in IDL type that denotes a byte of raw data.
5
In non-CORBA terminology, a default servant is often called a flyweight object.
6
POA policies are discussed in Chapter 6.
7
A CORBA::TRANSIENT exception means “temporary error; please try again later’.

Previous Up Next