Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Advanced CORBA Programming wit C++ - M. Henning, S. Vinoski.pdf
Скачиваний:
65
Добавлен:
24.05.2014
Размер:
5 Mб
Скачать

IT-SC book: Advanced CORBA® Programming with C++

This approach to POA creation keeps all your creation code in one spot rather than being scattered about your application. It also allows you to easily create POAs explicitly via find_POA or allows them to be created on demand as requests for their objects arrive.

11.13 POA Destruction

Eventually, POAs must be destroyed, usually because of an imminent shutdown of the ORB and the death of the server application process. However, POAs are not destroyed only at application shutdown. For example, an application that intends to remain alive might keep track of all the objects hosted by a given POA and then destroy that POA after all the objects previously created within it have been destroyed.

You destroy a POA using the POA::destroy operation. Invoking it on a POA also destroys all its descendant POAs. Any requests that are already being processed by objects within a POA being destroyed are allowed to complete, and any new requests will cause any parent POA adapter activators to be invoked, if present, or will result in a CORBA::OBJECT_NOT_EXIST exception being raised back to the client.

module PortableServer { interface POA {

void destroy(in boolean etherealize_objects, in boolean wait_for_completion);

// ...

}; // ...

};

The etherealize_objects parameter controls whether the POA takes action to also destroy any servants registered with it. This parameter is meaningful only if the POA has the RETAIN value for the ServantRetention policy and has a servant manager registered with it. If these conditions are true and if etherealize_objects is also true, the POA first effectively destroys itself and then invokes etherealize on the servant manager for each servant registered in its Active Object Map. The fact that the POA marks itself as destroyed first is important to ensure that any servants that attempt operations on the POA during their own etherealization receive a

CORBA::OBJECT_NOT_EXIST exception.

wait_for_completion, the second parameter to destroy, determines whether the operation waits for all requests currently in progress to finish. If true, it causes destroy to return after waiting for all requests already in progress to complete and for all servants to be etherealized. If wait_for_completion is false, the POA and its descendant POAs are simply destroyed, and the operation returns. Note that any requests in progress are still allowed to complete and any necessary etherealization of servants is carried out regardless of the value of the wait_for_completion parameter; it controls only whether or not destroy waits for these actions to complete before it returns to the caller.

457

IT-SC book: Advanced CORBA® Programming with C++

Unlike a POA whose POAManager has been transitioned into the inactive state, which cannot be reactivated, a previously destroyed POA can be re-created in the same process. This is because a POA is essentially a container for object-to-servant associations and normally does not encapsulate network resources as a POAManager can. Destroying a POA therefore destroys object-to-servant associations without necessarily shutting down or invalidating communications resources used by the application.

POA destruction can cause problems for applications that have poorly configured POA hierarchies. For example, if a parent POA has a ServantActivator that is an object registered with one of its child POAs, servant etherealization will be unable to complete correctly. Because the child POA hosting the ServantActivator is destroyed before its parent, the parent becomes unable to use the ServantActivator to etherealize its servants. POA implementations cannot detect this problem, so it is up to you to avoid creating this type of situation in your applications.

11.14 Applying POA Policies

The number of POAs in your application and the policies you choose for each one depend on several factors. Some of them are as follows:

The number of objects your application intends to support Expected rates and durations of requests

The underlying persistent store, if any, required by your objects

The level of resources and services supplied by the computer and operating system hosting your application

Any non-CORBA software your application must wrap or otherwise interact with

Some aspects of the distributed domain in which your application runs, especially if the ability to relocate objects into other servers in that domain is desired

We have left some of these factors vague for now, but we discuss details concerning each of them in the following sections. Note that we initially ignore the differences between persistent and transient CORBA objects because many POA issues do not depend on the value of the LifespanPolicy. We focus on issues related to POAs for persistent and transient objects in Section 11.14.5.

11.14.1 Multithreading Issues

A fundamental choice you must make for your applications is whether they are singlethreaded or multithreaded. This choice depends on several details, including the following:

Whether the underlying operating system and C++ language run time provide adequate multithreading support

The threading requirements of your ORB implementation

The tools you have available for debugging multithreaded applications

458

IT-SC book: Advanced CORBA® Programming with C++

Your levels of expertise and experience in creating and maintaining multithreaded applications

The capacity of any third-party libraries used in your application to work properly in a multithreaded environment

If your operating system, C++ language run time, or ORB does not support applications running in multithreaded environments, you must choose to make your applications single-threaded. Beware, however, that not all ORB implementations support both singleand multithreaded operation; some of them support only one or the other but not both. Also, not all ORBs adequately support applications that simultaneously act as both client and server. Such ORBs do not listen for incoming requests while the application is waiting for a response to a request it has made on another server. You must consult with your ORB documentation to determine the level of support your ORB provides for singlethreaded and multithreaded applications.

The threading choice you make for the whole application determines the values of the ThreadPolicy that you can meaningfully apply to your POAs. For example, making an application single-threaded disallows concurrent request processing even when a POA is created with the ORB_CTRL_MODEL value for its ThreadPolicy.

Even if multithreading support is available, you might still wish to use a POA with the

SINGLE_THREAD_MODEL value for the ThreadPolicy. If your servant implementations are based on third-party software that is not thread-safe and if you do not wish to implement code to serialize all calls to it, using the SINGLE_THREAD_MODEL guarantees that your servant invocations are serialized by the POA.

In general, we recommend using the ORB_CTRL_MODEL value for the ThreadPolicy. As explained in Section 11.4.7, this is the default if you do not specify a ThreadPolicy value at POA creation time. Specify the SINGLE_THREAD_MODEL only if you know that your ORB does not support multithreading and you are not concerned with porting your application to another ORB that does, or if your servants are not designed to support concurrent invocations.

In Chapter 21 we explain the POA threading models in much more detail. We also explore how your choice of whether your program is singleor multithreaded affects its throughput, performance, and scalability.

11.14.2 ObjectId Assignment

A simple rule for deciding whether a POA should have the USER_ID or SYSTEM_ID value for the IdAssignmentPolicy is to use system-assigned object identifiers for transient objects and use user-assigned identifiers for persistent objects. You typically use the USER_ID value for the IdAssignmentPolicy together with the PERSISTENT value for the LifespanPolicy because ObjectIds for persistent objects normally

459

IT-SC book: Advanced CORBA® Programming with C++

contain some indication of where you store the persistent state of the object. As we describe in Section 11.4.2, applications might use file system pathnames or database keys for ObjectIds for persistent objects. For transient objects, letting the POA assign ObjectIds is the easiest approach because your application does not normally use the generated identifiers directly.

As with all rules, however, this simple rule is not absolute. Applications can assign their own identifiers for use with transient objects, and POAs can also be created so that they assign identifiers for persistent objects. Using USER_ID with TRANSIENT can be helpful when the state of the transient objects is stored in an in-memory data structure rather than in the servants themselves. For example, for prototyping purposes we might write an application simulating our CCS that uses STL container classes to hold thermostat and thermometer data. In this case, we might want to use container keys as the ObjectIds for the transient objects our prototype creates.

At the other end of the spectrum, using SYSTEM_ID together with PERSISTENT is unusual and somewhat awkward. The generated identifiers will have no meaningful mapping into the problem domain of the application and thus may not be very useful as identifiers for the persistent storage areas of the objects. We therefore recommend that you avoid the use of the SYSTEM_ID value for the IdAssignmentPolicy with the

PERSISTENT value for the LifespanPolicy.

11.14.3 Activation

Using USER_ID together only with PERSISTENT, as we recommended in the preceding section, means that the same POA may not support the

IMPLICIT_ACTIVATION value for the ImplicitActivationPolicy. This is because IMPLICIT_ACTIVATION requires SYSTEM_ID. Fortunately, this is precisely what we want, because implicit activation of persistent objects suffers from the same problems as using SYSTEM_ID with PERSISTENT.

We recommend using IMPLICIT_ACTIVATION for POAs that also support the

RETAIN value for the ServantRetentionPolicy (required), the SYSTEM_ID value for the IdAssignmentPolicy (also required), the UNIQUE_ID value for the

IdUniquenessPolicy, and the TRANSIENT value for the LifespanPolicy. This is because using the _this function on a servant to implicitly create and activate transient objects is very handy for creating Policy objects, servant managers, iterators, and other transient objects. We recommend using the default NO_IMPLICIT_ACTIVATION for POAs that host persistent objects.

11.14.4 Space-Time Trade-Offs

Several POA policies are geared toward providing server applications with finegrained control over their space-time trade-offs on a per-POA basis. They allow trade-offs to be made concerning storage of ObjectId-to-servant associations and the number of

460

IT-SC book: Advanced CORBA® Programming with C++

application up-calls required to complete a single request invocation. This control is key to providing scalability for applications that host many objects or receive many requests. There are two primary aspects to the space and time required for POA request dispatching.

The time and space resources required for the POA to locate a servant associated with the ObjectId of the target object. This includes lookup in the Active Object Map, time required to invoke a servant manager, and time required to determine whether a default servant is being used.

The time and space required by the servant to determine which object it is incarnating for a given request.

For this analysis we ignore several costs:

Costs due to the unmarshaling of request parameters and the marshaling of the response Costs related to the lookup and possible activation of the target POA

Costs due to queuing requests (for single-threaded POAs) or acquiring mutex locks (for multithreaded POAs)

We also assume that the same request is invoked in each case and that it always takes the same amount of time to complete.

RETAIN with USE_ACTIVE_OBJECT_MAP_ONLY

With RETAIN, the POA stores associations between ObjectIds and servants in an Active Object Map. This not only consumes space but also—assuming the POA implements its map using some kind of hashing algorithm—requires the POA to do more than just a simple memory access to locate a servant for a request. Naturally, both the quality of the hashing algorithm and the number of associations stored in the map greatly influence lookup efficiency and the amount of storage the map occupies.

Note that under these circumstances, the value of the IdUniquenessPolicy does not affect the amount of storage required for the Active Object Map. This is because even if MULTIPLE_ID is in effect, each known ObjectId still requires a separate map entry. However, the use of MULTIPLE_ID affects the time it takes a servant to determine the identity of the object it is incarnating because it must access the POA Current object to obtain the target ObjectId. In a multithreaded environment, the POA Current is usually implemented using thread-specific storage, which can be costly to access.

RETAIN with USE_SERVANT_MANAGER

The combination of these policies can be the most expensive in terms of both space and time. In the worst case, when all objects hosted by the POA are invoked, the Active Object Map contains exactly the same entries it would if USE_SERVANT_MANAGER were not in effect, but the servant manager itself requires additional space and also adds time overhead to the initial request on each object. When the first request arrives for a given object, the POA first looks in its Active Object Map to find a servant to handle the

461

IT-SC book: Advanced CORBA® Programming with C++

request. Then, finding none, it invokes its ServantActivator to obtain a servant. After that, the servant is stored in the Active Object Map and the ServantActivator will not be invoked again for that target object.

The effects of the value of the IdUniquenessPolicy setting for this case are identical to the preceding case.

RETAIN with USE_DEFAULT_SERVANT

The space overhead for this policy combination depends directly on how many associations are stored in the Active Object Map. If most of the objects hosted by the POA are incarnated by the default servant, it means that there are few entries in the Active Object Map and storage requirements are minimized. On the other hand, if the default servant incarnates only a few objects, the Active Object Map holds many entries.

Time overhead for servant lookup is slightly different for this case than for the preceding case because there is no ServantActivator to gradually fill the Active Object Map over time. If a request arrives for an object that has no Active Object Map entry, the default servant is invoked, and the Active Object Map is not changed.

The effects of the value of the IdUniquenessPolicy setting for this case are almost identical to those for the preceding two RETAIN cases except for invocations made on the default servant. If the default servant incarnates the target object, it must always obtain the target ObjectId from the POA Current.

NON_RETAIN with USE_SERVANT_MANAGER

A NON_RETAIN POA has no Active Object Map, so storage requirements are minimized. However, time overhead can be significant because for each request, the POA must invoke its ServantLocator to obtain a servant. The amount of time required to obtain a servant depends almost entirely on the implementation of the ServantLocator. Also, unless the ServantLocator uses some sort of servant pool to manage servant instances, it must create and destroy a new servant on the heap for each request. This is not only costly in terms of time but may also increase the application's memory requirements due to heap fragmentation.

NON_RETAIN with USE_DEFAULT_SERVANT

This policy combination minimizes both space and time overhead. Space is minimized because the POA has no Active Object Map and all objects are incarnated with only a single servant. The time required to locate the servant is minimized because the POA need only access its default servant. However, a default servant must always determine the target ObjectId from the POA Current, so it may encounter time overhead due to thread-specific storage access.

11.14.5 Life Span Considerations

462

IT-SC book: Advanced CORBA® Programming with C++

Choosing whether your objects should be persistent or transient depends entirely on your application and the types of services it provides. Applications typically fall into one of two general categories.

Service-Oriented Applications

Applications that are service-oriented tend to support persistent objects that are very long-lived and stable. These objects are usually created once using either special options to the server program or using completely separate administrative programs. After they are created, the objects are advertised in the Naming Service (see Chapter 18), the Trading Service (see Chapter 19), or some other object reference advertising service. In fact, these services are themselves prime examples of service-oriented applications.

For example, the entire purpose of a Naming Service is to allow applications to access and modify the name bindings that have been registered with it. The name bindings registered with the Naming Service are normally kept in persistent storage, typically some form of database. Thus, a server that implements the Naming Service essentially presents the contents of this persistent storage as CORBA objects. ORB implementations usually support the Naming Service by supplying options to the Naming server program that allow it to be used to create a persistent NamingContext object. The resulting object reference can then be configured into the ORB as the root NamingContext that is returned from the ORB::resolve_initial_references method.

Service-oriented applications usually have two defining characteristics.

They are composed of long-lived objects that are created and destroyed via administrative tools.

The state of their objects is stored entirely within persistent storage.

Because such applications have persistent object state, they are almost always candidates for POA features such as servant managers and default servants, which allow applications to avoid creating a separate servant for each object they host. ServantLocators are especially useful for service-oriented applications because their preinvoke and postinvoke operations allow persistent state to be loaded before a method call on the servant and to be written back to the persistent store after the call completes.

Session-Oriented Applications

Some server applications are designed so that clients first create the objects they intend to use, use those objects, and then destroy them. Such applications are session-oriented because most of their objects live only as long as each client session lasts. Such objects are known only to the client that created them (and perhaps also to other applications that cooperate closely with the client).

In contrast to service-oriented applications, most objects hosted by session-oriented applications are created programmatically via requests on object factories. The factories themselves usually are persistent objects that are service-oriented and are thus advertised

463

IT-SC book: Advanced CORBA® Programming with C++

in the Naming Service or the Trading Service. Clients first use these services to find the necessary factories, and then they make requests on the factories to create the session objects they need.

Because they are intended to exist only for the duration of the client session, objects created within a session are transient rather than persistent. Being transient, these objects usually keep their state in memory rather than in persistent storage. If this ephemeral state is eventually made persistent, it is often written to persistent storage as the direct side effect of a client invocation on a session control object that provides a single point of control for the entire session.

Transient session objects normally supply operations that allow clients to explicitly manage their life cycles. For example, by deriving from the standard LifeCycleObject supplied by the OMG Life Cycle Service, interfaces can inherit standardized copy, move, and remove operations. See Chapter 12 for more details concerning the Life Cycle Service and general CORBA object life cycle issues.

Persistent Objects

An ORB implementation that supports persistent CORBA objects must be able to locate them and deliver requests to them even if the server applications that host them are not currently executing and must be started. This implies that applications hosting persistent objects do not operate in isolation. Instead, such servers must be registered with the ORB's Implementation Repository to allow the ORB to track the objects they host and to be able to activate them when requests are invoked on those objects. Chapter 14 provides details relating to Implementation Repositories and server activation.

Transient Objects

Unlike persistent objects, transient objects do not require significant support for location and activation. This makes them ideal for objects that are created only to deal with shortlived or localized activities. For example, iterator objects that provide clients with sequential access to container objects are usually implemented as transient objects. Also, Policy objects, other locality-constrained objects, and servant managers are best created as transient objects because they are only useful within the process in which they are created.

The standard policy values of the Root POA make it an ideal host for transient objects because it has the TRANSIENT value for the LifespanPolicy. It also has the SYSTEM_ID value for the IdAssignmentPolicy, and this means that the application need not create ObjectIds for objects it creates under the Root POA. Because the Root POA also has the IMPLICIT_ACTIVATION value for the

ImplicitActivationPolicy, the UNIQUE_ID value for the IdUniquenessPolicy, and the RETAIN value for the

ServantRetentionPolicy, it allows for simple object creation and activation via the servant's _this member function. Its USE_ACTIVE_OBJECT_MAP_ONLY value

464

IT-SC book: Advanced CORBA® Programming with C++

for the RequestProcessingPolicy eliminates the complexity of using servant managers or default servants. In general, the Root POA allows applications to handle simple CORBA objects in a manner that is clear and straightforward.

This is not to say, however, that transient objects are useful only with the Root POA. There are several meaningful uses for a POA that has policy values other than the TRANSIENT value that differ from those of the Root POA.

Because the Root POA has the ORB_CTRL_MODEL value for the ThreadPolicy, an application that wants all requests for its transient objects dispatched sequentially requires a POA with the SINGLE_THREAD_MODEL value.

An application could require a POA that hosts transient objects to have the USER_ID value for the IdAssignmentPolicy rather than the SYSTEM_ID value that the Root POA has.

It can also be useful to use policies other than UNIQUE_ID and RETAIN for POAs that host transient objects. If the state of a transient object is persistent and can be accessed via the ObjectId of the target object, using MULTIPLE_ID, servant managers, or default servants for transient objects can sometimes be a suitable approach. For example, in Section 11.7.3 we discuss making the Thermometer and Thermostat objects transient. Because their states are stored in the devices themselves, a servant-per-object approach would not be required to implement this solution.

Note, however, that an application that incarnates multiple transient CORBA objects using a single servant is somewhat unusual. A servant for a transient object usually holds its object's state in its class data members. Thus, it is not typical to use the

MULTIPLE_ID value for the IdUniquenessPolicy or to use the USE_DEFAULT_SERVANT or USE_SERVANT_MANAGER values for the

RequestProcessingPolicy for a POA that hosts transient objects.

Generally, it is best to create transient object references for those objects whose states are ephemeral or whose lifetimes are each bounded by some surrounding context. For example, iterator objects are often created as a side effect of a client invoking an operation to return a list of the contents of a container object. Both the OMG Naming Service (Chapter 18) and the Trading Service ( Chapter 19) use this idiom. Clients are expected to immediately use the iterator and not to expect it to exist for as long a time as its associated container will exist.

Although it is possible, you should think twice before using transient object references for objects that have persistent state. When a POA that creates a transient object is deactivated or destroyed, such as when the server application shuts down, any attempts by clients to invoke operations on that object will raise OBJECT_NOT_EXIST exceptions. This is misleading, given that the actual persistent state of the object may still exist, in which case it can most likely be accessed again by creating a new transient

465