Request Processing Aspects of CORBA and TAO (Part 1 of 2)

Request Processing Aspects of CORBA and TAO (Part 1 of 2)

by Rob Martin, Principal Software Engineer

February 2003

Introduction

CORBA ORBs are used in software systems that span a variety of problem domains including finance and banking, telecommunications network provisioning and maintenance, media collection and dissemination, aerospace, and defense [http://www.corba.org/success.htm]. Consequently, CORBA ORBs [formal/02-12-06] are subject to wide-ranging performance requirements, used in sequential as well as concurrent processing scenarios, and deployed on many different platforms. To be successful across such a broad range of applications, an ORB must provide some means of tailoring its behavior to a software application's specific requirements. Controlling how CORBA requests are processed is one way in which an ORB can be aligned to meet an application's needs.

Processing a CORBA request can be generalized as a two-step process:

  1. The ORB dispatches the request to a POA.

    To dispatch a request to a POA, an ORB selects a thread to process the request, identifies the target POA, and invokes the POA to process the request.

  2. The POA dispatches the request to an application object.

    To dispatch a request to an application object, a POA applies a threading model to enforce constraints on concurrent processing and a request processing policy to identify and invoke the target object.


The CORBA specification defines the POA's threading models, the policy used to specify a POA's applicable threading model, and the ORB's thread-related interfaces. The specification does not, however, define the strategies an ORB may use when assigning requests to threads or the mechanisms for choosing a strategy or combination of strategies that fulfill a specific application's requirements. All in all, the specification provides only a narrow treatment of these issues leaving much to the ORB implementer's discretion.

Modern, multi-threaded ORBs typically offer a variety of strategies for mapping requests to threads. Thus, much is also left to the application developer's discretion. Choosing appropriate strategies to control request processing behavior depends upon an application's characteristics:

Other factors may also influence or constrain request processing strategies:

Careful examination of an application's request processing characteristics and related factors is an important step when considering potential request dispatching strategies.

This is the first of two articles that address request processing aspects of CORBA and TAO. [The ACE ORB (TAO) is a standard-compliant CORBA ORB developed and maintained by the Distributed Object Computing Group (DOC Group) and commercially supported by OCI.] This article focuses on an ORB's thread-related operations and the POA's threading models as set forth by the CORBA specification. TAO's implementation of the standard, its most frequently used threading and concurrency strategies, and other commonly-used strategies that govern its request dispatching behaviors are the subjects of the second article. TAO's implementation is largely based upon software patterns that have emerged from distributed system and distributed real-time embedded (DRE) system research in recent years. The Related Topics section contains references to material that presents several software patterns of particular interest to readers wanting to understand or examine TAO's implementation. Some readers, especially those involved in DRE system development, may find the Real-Time CORBA Specification's (formal/02-08-02) threading and concurrent processing capabilities useful. (TAO includes a RT CORBA implementation, but that is beyond the scope of these articles.)

Thread-Related ORB Operations

 The ORB's thread-related operations are used to:

The relevant portions of the ORB's interface definition are:

  1. module CORBA
  2. {
  3. // Portions of the CORBA module omitted.
  4.  
  5. interface ORB
  6. {
  7. // Portions of the ORB interface omitted.
  8.  
  9. boolean work_pending();
  10. void perform_work();
  11. void run();
  12. void shutdown( in boolean wait_for_completion );
  13. void destroy();
  14. }
  15. }

ORB::run() provides a thread to the ORB. In appropriate circumstances, this operation can be called from multiple threads to provide more than one thread to an ORB. This is a blocking operation from the application's perspective. Each thread that calls ORB::run() becomes dedicated to the ORB. ORB::run() does not return until the ORB has shut down.

ORB::perform_work() provides a thread to an ORB for a single unit of work. This is a non-blocking operation from the application's perspective; the application can perform tasks un-related to the ORB between calls to ORB::perform_work(). The definition for a unit of work is left to ORB implementers; often it is a single invocation. ORB::perform_work() is best used in conjunction with ORB::work_pending() to implement a polling loop that interleaves ORB processing with other activities, such as servicing another external interface. ORB::work_pending() indicates whether or not an ORB has an immediate need for a thread to perform ORB-related tasks. (The CORBA specification states that ORB::work_pending() and ORB::perform_work() are intended for use only with the main thread. In practice, it isn't clear the extent to which this restriction is implemented.)

ORB::shutdown() instructs an ORB to halt processing. This operation is typically invoked just prior to the ORB's destruction. If wait_for_completion is TRUE, this operation blocks until the ORB has concluded request processing and other object adapter related activities, and all object adapters have been destroyed. If wait_for_completion is FALSE, ORB::shutdown() may return before the ORB completes its shutdown process. Some implementations may require a call to ORB::run() or ORB::perform_work() after a call to ORB::shutdown() with wait_for_completion == FALSE so the ORB can finish shutting down.

An ORB continues its normal processing tasks while it is shutting down so there may be a significant delay, after ORB::shutdown() is called, before the ORB actually stops processing. For this reason, some implementations may allow an application to specify a time limit for the shutdown operation. If the ORB has not shut down gracefully within the allotted time, the ORB may then conduct a forced shutdown.

ORB::destroy() destroys an ORB and releases its resources so they can be reclaimed by the application. This operation will initiate the shutdown process when called on an ORB that has not been shut down. If ORB::destroy()initiates the shutdown process, it blocks until the ORB has shut down, as if ORB::shutdown() had been called with wait_for_completion == TRUE.

Exceptions Raised by Thread-Related Operations

The following exceptions may be raised by thread-related operations or by circumstances arising from the use of thread-related operations:

Guidelines For Application Developers

Server and hybrid applications (hybrid applications act as a CORBA client as well as a CORBA server) must make one or more threads available to the ORB via ORB::run() or ORB::perform_work(). A thread from which ORB::run() is called becomes dedicated to the ORB. A thread that calls ORB::perform_work() can be used to perform other tasks in addition to ORB-related tasks. Pure client applications do not need to use the thread-related operations. (Applications that behave largely as clients but employ a callback object or Asynchronous Message Invocation [AMI] to receive a server's replies are considered hybrid applications.)

Some common scenarios are:

// Code fragment
for(;;)
{
  if(orb->work_pending())
  {
    orb->perform_work();
  }
  // do other tasks
}

In most cases, ORB::perform_work() should be called only when ORB::work_pending() returns TRUE to prevent the application from blocking in the absence of CORBA invocations. However, some ORB implementations may allow time-bounded calls to ORB::perform_work() and thus remove the need to call ORB::work_pending().

The run time of ORB::perform_work() is not deterministic. Application developers should pay careful attention to an ORB's implementation and their application's design when interleaving CORBA invocations with other processing tasks via ORB::perform_work(). Numerous factors, including long-running CORBA requests, outbound CORBA requests that are issued during the processing of an inbound CORBA request, and the ORB's definition of a unit of work influence the run time of ORB::perform_work(). Some implementations extend ORB::perform_work() to permit specification of a maximum run time, but typically this is not sufficient to place an absolute limit on ORB::perform_work()'s run time. For example, when a synchronous outbound request is issued during the processing of an inbound request, the run time for the inbound request becomes a function of the outbound request's run time, which is beyond the scope of the local ORB.

Terminating a CORBA-based application gracefully is often a complicated matter because the ORB continues normal processing while it shuts down. An application dedicated to processing requests can be signaled to shut down via a CORBA request. An application that performs tasks un-related to CORBA requests can be signaled to shut down via a CORBA request as well as any other interfaces that are serviced outside the scope of CORBA requests.

If signaled to shut down via a CORBA interface, ORB::shutdown() can be invoked with wait_for_completion == FALSE from the thread that processes the request; invoking ORB::shutdown() with wait_for_completion == TRUE would raise an exception in this situation. If signaled to shut down outside the scope of a CORBA request, the responding thread can invoke ORB::shutdown() with wait_for_completion == TRUE or FALSEwait_for_compltion == TRUE in this case will block the calling thread until the ORB has shut down.

Once ORB::shutdown() has been called, all calls to ORB::run() will return once the ORB has shut down. However, the time required to shut down an ORB depends upon the volume of client activity when shutdown is requested. In extreme cases, a complete cessation of client activity may be necessary to allow the ORB to shut down.

Here are some options for shutting down a CORBA-based application:

Request Processing

 A CORBA request is processed in two stages:

Strategies employed by a multi-threaded ORB as it receives and dispatches requests determine the extent to which requests may be processed concurrently. These strategies are usually described as a ratio between threads and requests, e.g.:

Each strategy has its strengths and weaknesses, which motivates understanding an application's request processing characteristics to choose the most appropriate strategy or combination of strategies. A complete discussion of request dispatching strategies is beyond the scope of these articles. The remainder of this article is devoted to the POA's threading models. The followup article describes important aspects of TAO's request dispatching capabilities. (For additional information regarding threading and concurrency strategies, see the Related Topics section at the end of this article.)

POA Threading Models

The POA threading models establish concurrency constraints that are imposed during request processing in a multi-threaded environment. These constraints further qualify the extent to which CORBA requests may be processed concurrently.

A POA's threading model is determined by its ThreadPolicy value. There are three standard ThreadPolicy values:


ORB Controlled Model

An ORB-controlled POA places no constraints on concurrent requests; the POA effectively abdicates responsibility for threading and concurrent request processing to the ORB.

Figure 1: ORB Controlled Threading Model

Figure 1: ORB Controlled Threading Model
 

Concurrent servant upcalls may occur at the POA level in multi-threaded environments if the ORB dispatches requests concurrently. Application objects activated by an ORB-controlled POA in a multi-threaded environment should be multi-thread safe.


Single Thread Model

A single-threaded POA constrains request processing such that concurrent upcalls shall not occur at the POA level, even in multi-threaded environments.

Figure 2: Single-Thread Threading Model

Figure 2: Single-Thread Threading Model
 

A multi-threaded ORB may dispatch concurrent requests to a single-threaded POA, but the subsequent servant upcalls will occur sequentially; thus an application object activated by a single-threaded POA will not be subject to concurrent requests. However, an application object activated by multiple single-threaded POAs may be subject to concurrent requests in a multi-threaded environment.

Figure 3: Srvant Activated by Multiple Single Threaded POAs

Figure 3: Servant Activated by Multiple Single-threaded POAs
 

Activating an application object with multiple POAs is not recommended.


Main Thread Model

Requests are dispatched to all main-threaded POAs sequentially. This effectively serializes requests dispatched by main-threaded POAs at the ORB level.

Figure 4: Main Thread Model

Figure 4: Main-Thread Model
 

An application object activated by a main-threaded POA will not be subject to concurrent requests. The main-threaded model is also applicable in processing environments where some code must be executed on the main thread. In such environments, the main-thread model insures that servant upcalls are processed on that thread. However, the application must make the main thread available to the ORB via ORB::run() or ORB::perform_work().


Controlling a POA's Threading Model

A POA's threading model is determined by its ThreadPolicy. The default ThreadPolicy value is ORB_CTRL_MODEL. A POA's ThreadPolicy, like all other POA policies, can be assigned only when the POA is created.

The relevant portions of the PortableServer module and the POA's interface are:

  1. module PortableServer {
  2.  
  3. // Portions of the PortableServer module omitted.
  4.  
  5. const CORBA::PolicyType THREAD_POLICY_ID = 16;
  6.  
  7. enum ThreadPolicyValue {
  8. ORB_CTRL_MODEL,
  9. SINGLE_THREAD_MODEL,
  10. MAIN_THREAD_MODEL
  11. };
  12.  
  13. local interface ThreadPolicy : CORBA::Policy {
  14. readonly attribute ThreadPolicyValue value;
  15. };
  16.  
  17. local interface POA {
  18.  
  19. // Portions of interface POA omitted.
  20.  
  21. POA create_POA(
  22. in string adapter_name;
  23. in POAManager aPOAManager,
  24. in CORBA::PolicyList policies
  25. ) raises (AdapterAlreadyExists, InvalidPolicy);
  26.  
  27. ThreadPolicy create_thread_policy(
  28. in ThreadPolicyValue value);
  29.  
  30. };
  31. };

The following code fragment demonstrates how to create a single-threaded POA:

  1. // Portions of int main(int, char **argv) omitted.
  2.  
  3. // Init the ORB.
  4. CORBA::ORB_var orb = CORBA::ORB_init (argc, argv);
  5.  
  6. // Get the Root POA.
  7. CORBA::Object_var obj = orb->resolve_initial_references ("RootPOA");
  8. PortableServer::POA_var poa = PortableServer::POA::_narrow (obj.in());
  9.  
  10. // Create and populate a policy list.
  11. CORBA::PolicyList policies(1);
  12. policies[0] = poa->create_thread_policy(PortableServer::SINGLE_THREAD_MODEL);
  13.  
  14. // Use the Root POA's POAManager.
  15. PortableServer::POAManager_var mgr = poa->the_POAManager ();
  16.  
  17. // Create a child POA.
  18. // Threading model is single-threaded.
  19. // All other policies assume the default value.
  20. PortableServer::POA_var st_poa = poa->create_POA("ST POA", mgr.in(), policies);
  21.  
  22. // Release memory allocated to the policy list.
  23. policies[0]->destroy();

Guidelines for Application Developers

The ORB-controlled threading model (ORB_CTRL_MODEL) allows application developers to make the most of an ORB's concurrent processing capabilities and performance optimization strategies. An application using this threading model can also take advantage of fine-grained contention management techniques to resolve application objects' contention for shared resources and services. However, this model obligates application developers to assume concurrent requests will occur, and identify and resolve points of contention that may arise during concurrent CORBA requests.

The following scenarios might motivate use of the ORB-controlled threading model for a CORBA-based application that is subject to concurrent requests:

The ORB-controlled model might also be appropriate when the ORB offers a serialized request dispatching strategy that out-performs single-threaded or main-threaded POAs.
 

The single-threaded model (SINGLE_THREAD_MODEL) can simplify an application's design and implementation by guaranteeing that an application object will not be subject to concurrent invocations from a distinct POA. Applied within its constraints, this model shifts some responsibility for contention management away from the application. However, this mechanism can only resolve contention for distinct servants arising from concurrent requests; it can not resolve contention for services and resources shared by application objects. Resolving contention for commonly-used services and resources remains the application's responsibility.

The single-threaded model is appropriate when:

 

The main-thread model (MAIN_THREAD_MODEL) is applicable in two circumstances:

This model imposes the most stringent constraints upon an application and likely reduces a multi-threaded ORB's efficiency. Application developers may want to compare performance of this threading model with that of a single-threaded ORB before committing to this model.

 

Related Topics

Chapter 21 of Advanced CORBA Programming with C++ [ISBN: 0201379279] by Michi Henning and Steve Vinoski discusses issues associated with multi-threaded applications. This text was written to be compliant with an earlier version of the specification (the current version is 3.0) but the authors' treatment of these issues is relevant to contemporary applications.

Pattern-Oriented Software Architecture, Volume 2 [ISBN: 0-471-60695-2] by Douglas Schmidt, Michael Stal, Hans Rohnert, and Frank Buschmann presents many architectural patterns that are relevant to the topics discussed in these articles. Although these patterns are not presented in the context of a CORBA ORB, they respond to the forces that affect an ORB and are therefore applicable to ORB implementations. In many cases, an ORB is cited among the known uses of these patterns. The Reactor pattern (pg. 179) defines an architecture that is used for demultiplexing and dispatching network events. The Half-Sync/Half-Async pattern (pg. 423) defines a competing architecture for demultiplexing and dispatching network events. The Leader/Follower pattern (pg. 447) defines a means of managing a thread pool suitable for use within an ORB. The Active Object pattern (pg. 369) defines a technique suitable for implementing a thread-per-connection strategy.

Readers interested in additional material related to threading and concurrent processing may find the following research papers useful:

Summary

This article examined aspects of CORBA request processing related to threading and concurrent processing as set forth by the CORBA specification. The material presented here discusses the ORB's thread-related operations and their use as well as the definition and use of the POA's threading models. Guidelines are offered to help developers consider an application's requirements and choose strategies that will fulfill those requirements. The followup article describes TAO's request-processing strategies and its implementation of the ORB's thread-related operations.

The Middleware News Brief is a periodic newsletter. The purpose and intent of this publication is to advance and inform on features, news, and technical information about Open Source, middleware technologies (e.g., TAO, OpenDDS, JacORB), including case studies and other technical content.

© Copyright Object Computing, Inc. 1993, 2016. All rights reserved

Subscribe

secret