Archived

This forum has been archived. Please start a new discussion on GitHub.

dynamicCasting an out parameter re-passed as an out param?

Sorry for the confusing topic title - couldnt think of a better one though.

I have 3 ice enabled applications (A, B and C), the first two of which (A and B) communicate indirectly with each-other through the third.

To accomplish this, the classes that these applications are made from define something kind of like this:
void send(const Request& req);

// resp is an "out" parameter
void handle(const Request& req, response& resp) {
    ..
    ObjectPrx app;  // if called from C, pretend this is a valid proxy to A or B.
                          // if called from A or B, pretend its a proxy to C.
    app->handle(req, resp);

The send command gets a proxy for one of the remote applications (depending on the contents of the request) and invokes that application's handle() method.

for A and B, send() always causes handle to be invoked on C which acts as a proxy between A and B.

Inside of C's handle() method, another call to the destination object's handle() method is made, receiving the sameparameters that were passed into C's handle().

The Request and Response classes referenced above are base classes. I might pass in a SpecialRequest or SecretResponse object to these methods.

Now A sends a command that expects SecretResponse back. Once A gets the response back from C, which is simply passing back the response it got from B, I cannot seem to dynamicCast() it back to the SecretResponse type that B instantiated; the cast returns NULL, which according to the documentation means that the conversion is not possible. I'm wondering if the "SecretResponse" part of the Response is being sliced off by C's "proxying", or something along those lines, and if so, what I can do to avoid that.

Comments

  • halfhp wrote: »
    The Request and Response classes referenced above are base classes. I might pass in a SpecialRequest or SecretResponse object to these methods.

    Now A sends a command that expects SecretResponse back. Once A gets the response back from C, which is simply passing back the response it got from B, I cannot seem to dynamicCast() it back to the SecretResponse type that B instantiated; the cast returns NULL, which according to the documentation means that the conversion is not possible. I'm wondering if the "SecretResponse" part of the Response is being sliced off by C's "proxying", or something along those lines, and if so, what I can do to avoid that.

    What are the relevant Slice definitions? (You didn't show them.) It looks like the Request and Response types are Slice classes, not Slice interfaces?

    You don't state it explicitly, but it sounds like A and B have knowledge of SpecialRequest and SecretResponse, but C only compiles the Slice definitions for Request and Response.

    Now, if these types are Slice classes, then the behavior you are seeing is exactly what is expected. If A sends a SpecialRequest class instance to C, C doesn't have type knowledge of SpecialRequest and, consequently, unmarshals only the base Request part of the instance. Then, when C passes the instance to B, what is marshaled is only that base part because, when C received the instance, it sliced off the derived part.

    This is exactly analogous to what happens if you pass a derived C++ class instance by value to a function with a formal parameter type that is a base type of the instance. The derived part of the instance is lost because it is sliced off.

    I'm a little surprised to see this signature in your code though:
    void handle(const Request& req, response& resp)
    
    Normally, class instances are passed as their Ptr type, so I expected to see
    void handle(const RequestPtr& req, ResponsePtr& resp)
    

    If you don't use a Ptr type, especially for the out-parameter, you can end up leaking memory or dereferencing already deallocated memory.

    You should review the section on smart pointers in the manual to make sure your code is correct in this respect.

    In summary, you cannot pass Slice class instances polymorphically by value unless all links in the call chain are compiled with the Slice definitions of the most derived type of the class. If any link in the chain only knows a base type of the class, all instances passing through that link are sliced to that base type.

    If you don't want such slicing, you can either avoid passing instances by value and pass proxies to an interface instead, or you can package up your class instance into another type (such as a byte array or sequence of name-value pairs) and transmit it as that value.

    With the first approach, the state for the instance remains in a single server and you simply pass a proxy to the relevant interface around. Proxies can be passed polymorphically even if not all links in the call chain have knowledge of the most derived type of the interface. The cost of this idea is that, to access the state, clients incur an extra RPC to the server to retrieve that state.

    With the second approach, you are still passing the instance by value, but you have to explicitly marshal and unmarshal the special derived part that is represented as a byte array or name-value sequence before transmission and after receipt.

    Of course, the third (and easiest) approach would be to compile C with the Slice definitions of SpecialRequest and SecretResponse. If you do that, the class instances will be unmarshaled and remarshaled by C without slicing.

    Cheers,

    Michi.
  • Hi Michi - thanks for the reply!

    Sorry for omitting those details - I am indeed using Ptr to the slice generated classes for both the request and response parameters.

    Regarding C's knowledge of A and B, you guessed right - it is not compiled with any definitions for class specializations either module defines, only the base classes are known to it, which is key to the design we are trying to achieve so option #3 is out.

    Sounds like I'll have to decide whether the overhead of an extra RPC call or the overhead of marshalling/unmarshalling to/from a byte array is the better way forward.
  • One quick question about the proxy approach:

    Since modules A and B have no direct connection to each-other, how would A be able to use one of B's proxied object that was passed to it by C?
  • benoit
    benoit Rennes, France
    Hi Nick,

    If A and B don't have direct connections to each other and have to go through C, you'll need to implement some kind of routing to make sure requests from B on proxies from A are routed through C.

    Does C need to unmarshall the request/response base classes? What is C trying to achieve here? Did you consider using blobjects instead?

    For more information on Ice routing and a concrete example, you could take a look at the issue #14 of the Connections newsletter. Note that the example is a bit old: it is not necessary anymore to use the mechanism presented in the article to make non-blocking invocations from a GUI event loop (since Ice 3.3, AMI requests are guaranteed to be non-blocking). Nevertheless, it should give you a good idea on how the routing can work.

    Cheers,
    Benoit.
  • Hi Benoit,
    If A and B don't have direct connections to each other and have to go through C, you'll need to implement some kind of routing to make sure requests from B on proxies from A are routed through C.

    When you say requests from B on proxies to A, I assume you mean method/param calls that B does on an A proxy that it received through C (the response object changed to a Pxy type mentioned in the original post). If I were to proxy those kinds of operations through C, I would need C to know A's specialization to make the call, no?
    Does C need to unmarshall the request/response base classes? What is C trying to achieve here? Did you consider using blobjects instead?

    C needs to interact with the request base class but not the response base class. The reason being that C contains a state machine that can intercept messages and "short-circuit" the message flow based the state it is in. In our code request is actually referred to as a Command and contains a state table. Also there may be more than one "C"; the path from A to B can have n hops. On each hop the state table is compared against that module's current state to decide how the message is routed. I know its a little strange to put the state table inside of the command it's self, but our design requires it and security is not a concern for us.

    EDIT:
    PS - Reading about blobject now.
  • benoit
    benoit Rennes, France
    halfhp wrote: »
    Hi Benoit,

    When you say requests from B on proxies to A, I assume you mean method/param calls that B does on an A proxy that it received through C (the response object changed to a Pxy type mentioned in the original post). If I were to proxy those kinds of operations through C, I would need C to know A's specialization to make the call, no?

    Not necessarily, C could be using blobjects to forward the calls on the Response or Request interfaces.
    C needs to interact with the request base class but not the response base class. The reason being that C contains a state machine that can intercept messages and "short-circuit" the message flow based the state it is in. In our code request is actually referred to as a Command and contains a state table. Also there may be more than one "C"; the path from A to B can have n hops. On each hop the state table is compared against that module's current state to decide how the message is routed. I know its a little strange to put the state table inside of the command it's self, but our design requires it and security is not a concern for us.

    EDIT:
    PS - Reading about blobject now.

    It's difficult to say which approach is best without knowing more but it sounds like using Slice classes would be simpler.

    Perhaps the best is to have C implement a Blobject. The implementation would use the Ice streaming API to un-marshall the "request" in-parameter of the "handle()" invocation. This way, C can unmarshall the command base class and decide whether or not to forward the request to another peer. If it decides to forward the request, it would forward it using the ice_invoke proxy operation with the in-parameter byte sequence. If it decides to not forward the request, it would have to marshall an appropriate response using the Ice streaming API.

    The problem with this solution is that it requires having a bit of knowledge of the encoding of Ice invocations.

    Another more flexible solution which wouldn't require such knowledge would be to use both a blobject and a dispatch interceptor. The implementation of the dispatch interceptor first dispatches the request to the servant that implements the handle() method. The implementation of this method decides if the request can be forwarded to B. If it can't, it returns an appropriate result object (or throw a user exception depending on your application). If it can forward it, it would throw a special exception ("ForwardException" for example) which would be catch by the dispatch interceptor implementation. Upon catching this exception, the dispatch interceptor would dispatch the request to a blobject servant that would take care of forwarding the request "as-is" to B.

    See the Ice manual for more information on blobjects and dispatch interceptors (the test/Ice/interceptor test has some code to show the use of dispatch interceptors).

    Cheers,
    Benoit.
  • I like the idea of blobject, but I dont think I need the complexity of ice_invoke; request and response objects never have any methods defined in slice; only data members. (to get away from using factories to generate specializations)

    Perhaps a "wrapper" class for request and response with the classId and a sequence of bytes containing the serialized object would be sufficient? Unless I am missing something, thats basically what blobject is if you take away ice_invoke?
  • benoit
    benoit Rennes, France
    Hi,

    If I understand your suggestion correctly, your request class would contain attributes for the state needed by C and a byte sequence that contains the command specialization. Correct? If this is the case, then yes it's another option. You can use the Ice streaming API to unmarshall/marshall the command object into this byte sequence.

    Cheers,
    Benoit.
  • Do exceptions work differently by any chance? I seem to recall being able to catch a BaseException in Module C that was thrown by Module B as DerivedException, then have C rethrow the BaseException it caught back to A and then have A catch it as a DerivedException.
  • Nevermind - a little trial and error answered that question :(

    This dynamic exception stuff seems like a tougher problem to solve than the original problem. With the original problem it was pretty easy to add serialization/deserialization of my objects behind the scenes since I can just modify the send() and handle() methods to do it automatically. But exceptions are sent and received using throw/catch keywords. I dont see a clean solution for this. Is there one?
  • I just found the part of ice_invoke that mentions this very subject!

    Instead of directly calling modulePxy->receive(...), if I call ice_invoke, passing inParams and outParams, I could get the derived exception, yes? And to inspect the exception at the middle-man, I can always marshall outParams to my ExceptionBase, yes?
  • If a Blobject subclass intends to decode the in parameters (and not simply forward the request to another object), then the implementation obviously must know the signatures of all operations it supports.

    What!? So does this mean I cannot decode into base classes? That doesnt match my experiences with inputstream.
  • one last question to add to the barrage:

    Sticking with the above pseudo code, am I allowed to make C extend both Ice::Blobject and a Slice class? I am getting this error when I try:
    error: no unique final overrider for ‘virtual Ice::DispatchStatus Ice::Object::__dispatch(IceInternal::Incoming&, const Ice::Current&)’
  • benoit
    benoit Rennes, France
    Hi

    Once an exception is "sliced", re-throwing it will transmit the sliced exception over the wire.

    If you want to preserve the original exception or original in-parameters, the solution is to use blobjects to get the in and out data as a byte sequence (this includes parameters and user exceptions). Your code can then manually un-marshall/marshall the data using the Ice streaming API if it knows the signature of each Slice operations. In other words, it must know the signature of each Slice operation to be able to decode the in parameters, (the handle() method has a single request parameter, so your code will try to un-marshall an object from the in param byte sequence). If not all the Slice classes are known during the decoding, the decoded objects will be sliced.

    It's not possible to both extend the blobject servant and a servant generated from a Slice interface. An Ice server can only dispatch a request to a single servant... that is, unless you use dispatch interceptors as I suggested in my previous post.

    With a dispatch interceptor, you could:
    • First dispatch the request to the generated servant, this will take care of the un-marshalling of the request and the marshalling of the response if the implementation decides to send a response.
    • If the implementation decides to forward the request to another peer, it would throw a special exception which would be catch by the dispatch interceptor. The interceptor would then retry the dispatch a the blobject servant whose implementation would forward the request "as-is" to the other peer

    So basically you have these 3 solutions:
    • Use a Slice request base class with a byte sequence attribute. The byte sequence attribute contains the data that can only be un-marshalled/marshalled by A or B (using the streaming API).
    • Use only blobjects and un-marshall manually the in parameters to get the request base class request (and eventually marshall manually the response if no forwarding is needed).
    • Use blobjects and dispatch interceptors as described above.

    Cheers,
    Benoit.
  • thanks for all the help - the blobject solution has been implemented and works as advertised.

    Nick