[Up: Reference Implementation]
[Previous: Persistence] [Next: Other Implementations]

Collocation

Location transparency is one of the corner stones of CORBA. The client is not aware of a servant's location, the ORB alone is responsible to read an object reference's profiles to contact the implementation. As described in section 3.3, auto-generated stub classes package their parameters in a CORBA request, which is then transported by the ORB and decoded by the skeleton on the server side.

This is fine for remote invocations, but suboptimal for local invocations, if the servant happens to be in the same process as the client, be it coincidence or by design. While performance optimizations might not be necessary for the random case, inefficient handling is annoying if a server knowingly manipulates local objects through CORBA calls.

An example is the Naming Service, which maintains a tree of ``Naming Context'' objects. These must be CORBA objects, since they must be accessible from remote. But if the Root context is asked to locate a node, it needs to traverse the tree, resulting in unsatisfactory performance if each step required a CORBA invocation to occur. In the local case, marshalling and unmarshalling parameters to and from a request seems overly complicated when the data could be used directly.

Effort to circumvent normal ORB request handling is well spent and allows for more scalability, because the user can easily migrate between efficient local invocations and ORB-mediated remote invocations.

Figure 5.10: Common strategy for Collocation using the BOA
\includegraphics{pics/boa-collocation.eps}

Figure 5.11: Collocation with the POA requires a Proxy
\includegraphics{pics/poa-collocation.eps}

Most ORBs, including MICO, implement collocation optimizations for BOA-based objects using the strategy shown in figure 5.10, which shows the inheritance for the stub and skeleton classes for interface ``Account.'' By using the same inheritance from CORBA::Object and the same interface from the common base class Account, stub object and servant can be used interchangeably.

If a client receives an object reference, the ORB checks if the object is local, by comparing the reference's profiles with its own object reference template. If this comparison fails, the ORB falls back to generating a stub object. Otherwise, if the object is indeed local, it contacts the BOA to return the actual servant. The client cannot distinguish the servant from a stub: the interface matches, and the method signatures are the same.

The only difference is speed: if the client performs an invocation, the servant is called directly, thanks to C++'s abstract classes and virtual methods. The ORB is never involved, and no data is moved around; the location-transparent remote invocation decays to a local procedure call, without any overhead.

However, this inheritance-based approach is not possible with POA-based objects, for several reasons:

Another obstacle raised in CORBA 2.3 is that valuetype parameters may need to be copied. As a solution, a proxy is introduced to control access to the servant [10]. For each interface, the IDL compiler creates a collocation proxy (Account_stub_clp in figure 5.11). This class is a stub that specializes the normal stub class in the case of a collocated servant. Whenever the client acquires an object reference as the result of a method invocation or ORB operation, the ORB produces a collocation proxy if the reference is local and if the servant is adapted through a POA.

Figure 5.12: Abbreviated Sample code from a Collocation Proxy
\begin{figure}
\hrule\vspace{3mm}
\par\verbatiminput{include/clp-code.cc}\par\hrule\par\end{figure}

For the purpose of collocation using the BOA, MICO already provided the skeleton method in the generic Object Adapter interface to produce the (BOA-based) servant. This part of the Object Adapter interface was first useless with the POA, but then found to be useful for collocation, as it made the process of acquiring a collocation proxy transparent to the client.

The collocation proxy uses delegation instead of inheritance to invoke the servant. Before an invocation is actually performed, the proxy has to cooperate with the servant's POA to find out if a direct procedure call can proceed:

  1. Make sure the servant's POA that we refer to has not been destroyed.
  2. Check that the POA is in the active state.
  3. Ask the POA to retrieve the servant from its Active Object Map.
  4. Verify that the servant is implemented through a static skeleton and not using the DSI.
  5. Update the POACurrent settings to reflect the invocation in progress.
Only after the checklist is complete, the proxy can delegate the invocation to the servant, by invoking the skeleton's virtual methods. If any of the above steps fails, the collection proxy falls back to the default stub's behavior by calling its parent class, where the parameters are marshalled into a CORBA request and fed to the ORB as usual, guaranteeing correct behavior in all circumstances. An example of the code generated by the IDL compiler is shown in figure 5.12, abbreviated by the removal failure handling for presentation purposes.

Performance measurements for a simple operation show that method invocation through the collocation proxy is about 40 times faster than an ORB-mediated invocation. This factor would actually be greater for operations with more complex parameters, because no marshalling needs to be done, and the complexity of the above steps is constant. However, if the computations performed in the operation become more complex and exceed the time saving from 200\( \mu \)s to 5\( \mu \)s (exemplary values on the author's system), the performance impact becomes less significant.

So far, the motivation for collocation has been performance, but collocation proxy classes are also convenient for locality-constrained objects, where the servant must be in the same process as the client.

Where not directed by the specification, MICO omits skeleton and stub classes when generating code for locality-constrained objects like the POA. There is only a single thread of inheritance, and the servant is called directly. This is not possible for servant managers and adapter activators, which the specification requires to follow the ``usual'' semantics, so that users can derive their object implementation from the skeletons. Yet no stubs and skeletons can be generated: the parameters handled by servant managers are native and cannot be marshalled.

In the first implementation, hand-written skeleton classes were used that could double as stubs, exploiting a special exception in the POA's activation mechanism, but bypassing POA mediation of method invocation. These were later replaced by code derived from an IDL-generated collocation proxy, with the inheritance from and delegation to the normal stub - which does not exist - removed.

Valuetypes as introduced by the Objects by Value specification in CORBA 2.3 [30] will present an additional challenge to the collocation mechanism. The major factor for increased performance in collocation is that a method's parameters do not need marshalling but can be used directly. This is not always possible with valuetypes, since they may be shared: the same valuetype can be referenced more than once, so that if the valuetype is changed using one reference (a C++ pointer), the other reference is affected, too. This is not in itself surprising but the same behavior as with normal, remote objects handled through object references.

But false behavior would result if a shared valuetype is passed as an inout parameter to a collocation proxy. If the shared valuetype was passed to the servant and there modified, as explicitly allowed in the C++ mapping [31], the other copy of the valuetype would, as an invalid side effect, change, too.

Figure 5.13: Valuetypes (Objects by Value) can be shared across Parameters.
\begin{figure}
\hrule\vspace{3mm}
\par\verbatiminput{include/obv-sharing.txt}\par\hrule\par\end{figure}

The collocation proxy must therefore check if valuetypes passed as an inout parameter are shared, and create a duplicate if so. Not only is the duplication of a valuetype complex in itself, - it frequently requires marshalling and unmarshalling of the valuetype - but introduces new problems, as valuetypes can also be shared across parameters. If the same valuetype is used twice as parameter in a method invocation, the servant must receive a shared parameter, too. An example is shown in figure 5.13, where the same valuetype is passed both as the first and the second parameter. If the second parameter needs to be copied in the collocation proxy, the sharing must be detected, and the copy must be passed as first parameter, too, when invoking the servant.

Parameter sharing is not always as obvious as in this example, as valuetypes can describe complex graphs, and sharing may occur between nodes of different graphs, between members of other types like structures or arrays, further masked by inheritance: a base valuetype can be shared with a derived valuetype.

Code has been added to the IDL generator to make the collocation proxy as smart as possible in detecting parameter sharing. If the IDL generator decides from a parameter's signature that either no or easily detectable parameter sharing can occur, extra code is produced to make copies of shared parameters as necessary. But if the possibilities become too overwhelming, the IDL compiler gives up and does not generate a collocation proxy at all.

Because of the many issues involved, the code involved both in the IDL generator and the generated code is complex and has not yet been fully verified, and maybe it would be sensible to keep the code small and to skip collocation optimizations altogether where valuetypes are involved.


[Previous: Persistence] [Next: Other Implementations]
[Up: Reference Implementation]

Frank Pilhofer
1999-06-23