Note: this is NOT issue 3754, but it is related. A new issue is needed
as described below:
--------------------------------------------------------------------------
Issue 3754 (colocated call optimization and portable interceptors) was
raised some time ago by Harold Carr. This issue discusses the problems
of supporting portable interceptors for RMI-IIOP calls when the colocated
call optimization is used. The basic issue is that there is no way for the
ORB to detect whether a local dispatch to a servant completed normally or
completed by throwing an exception. This problem exists because the
current is_local/servant_preinvoke/servant_postinvoke mechanism has no
way to report normal or exceptional completion of a local dispatch.
The same problem arises in the IDL stub case as well.
Both Java mappings can share the same solution here, but a solution requires
changing part of the portable stub API which is shared by both mappings.
In the discussion of 3754, Simon Nash asked to reassign the issue from the
Java to IDL RTF to the Java mapping RTF. We really need two separate issues
here. I propose that we do the following:
1. Move 3754 back to the Java to IDL RTF since Harold wrote the issue with the
RMI-IIOP mapping in mind.
2. Create a new Java mapping issue for the same problem. The bulk of this message
should be the new issue.
Both RTFs will need to consider the issues. The new Java mapping issue will need
to propose extensions to the portable stub APIs and document the required changes
in the IDL based stubs, while the resolution to 3754 will need to adopt the
same new portable stub API and document the required changes in the RMI-IIOP stubs.
To start a discussion, here is my initial proposal for solving this problem in
the Java mapping RTF:
We need to add methods to report the result of the local dispatch. I propose that
we extend the portable definition of ServantObject as follows:
abstract public class ServantObjectExt extends ServantObject
{
abstract public void normalCompletion() ;
abstract public void exceptionalCompletion( Throwable thr ) ;
}
Another alternative is to extend the Delegate class instead. I prefer the
ServantObject approach because the method signatures are simpler:
adding these methods on the Delegate would require passing ServantObject
as an argument, since multiple threads could be simultaneously invoking
methods on the same object reference, which would share the same delegate.
Making this change makes it necessary to consider what happens in
mixed versions.
Old stub, Old ServantObject:
no change
New stub, new ServantObject:
stub always invokes either normalCompletion or
exceptionalCompletion, so the ORB has enough information to correctly implement
portable interceptor behavior. Note that a location forward (e.g. from a
ServantManager) is not a problem today, since it happens inside of
_servant_preinvoke,
which allows the ORB to correctly handle that case.
New stub, old ServantObject:
If a new stub is written to do an instanceof check on
the ServantObject it gets from _servant_preinvoke, it can correctly determine
whether or not to call the new methods. There are a number of possibilities
for the check:
1. Just use (so instanceof ServantObjectExt) and rely on the ServantObjectExt
class to be defined somewhere in the environment. New stubs that wanted to
be compatible with old ORBs would need to be packaged somehow with this
class in their classpath. The stubs would need the ServantObjectExt class
both at compile time and at runtime.
2. A stub could be written to entirely use reflection for the instanceof check
and the method invocation. This would avoid the requirement to have
ServantObjectExt in the classpath, but would require more code in the stubs,
plus run a little slower (probably about 2X slower in JDK 1.4, but in
earlier JDKs the penalty could easily be 10-20X).
Old stub, new ServantObject:
In this case, neither method is ever invoked, so the
ORB can tell the difference between normal completion and an old stub.
The PI implementation in this case is free to do anything. However, probably the
best approach is to extend RequestInfo::reply_status with a new value
PortableInterceptor::UNKNOWN. Then the local case would always use the
interceptor points send_other and receive_other with a reply_status set to
UNKNOWN. This extension requires a new core issue.
It is worth noting here that the PI change makes it reasonable to consider simply
changing PI, and avoid changing the ServantObject API and 2 language mappings.
In this case, a user of PI cannot tell what is happening with colocated calls
in a very basic sense: did the operation complete successfully or not?
Many applications of PI (for example, transactions and security) often do not
require the use of PI in the colocated case. However, one important application
is best done this way: logging/tracing in a complex system. I believe this
argues strongly for extending the mapping, since a log entry that cannot distinguish
success from failure is of little use.
With the instanceof check, the local code for the sample stub then becomes
(from page 1-114 of ptc/01-06-04):
org.omg.CORBA.portable.ServantObject _so =
_servant_preinvoke( "length", _opsClass ) ;
if (_so == null)
continue ;
Example.AnInterfaceOperations _self =
(Example.AnInterfaceOperations)_so.servant ;
try
{
int result = _self.length(s) ;
if (so instanceof ServantObjectExt)
((ServantObjectExt)so).normalCompletion() ;
return result ;
}
catch (Throwable thr)
{
if (so instanceof ServantObjectExt)
((ServantObjectExt)so).exceptionalCompletion( thr ) ;
throw thr ;
}
finally
{
_servant_postinvoke( _so ) ;
}
Note that the instanceof checks are quite fast in modern JVMs,
so I don't think this adds significantly to the overhead of the operation.
A reflective solution is also possible, but I am not including that here.
In any case, the precise code generation should be left to the implementation.
All that the spec should require is:
1. If the invocation on the servant completes without throwing an exception,
then the stub code must call servant.normalCompletion after the invocation
completes.
2. If the invocation on the servant throws exception exc, then the stub code
must call servant.exceptionalCompletion( exc ) after the invocation completes.
In either case, the servant completion call must occur before the
_servant_postinvoke call.