Archived

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

Bidirectional vs. Callback

Hi there,

I am relatively new to ICE, so please bear with me.
I checked all your examples located under /demo/ice and I have to state that they are couple of things I am still not clear about.
They are as follows:
1. In case of a bidirectional invocation, you implement servant as a Thread (run() method is provided), but in case of a Callback such thread does not exist. Why?
2. In one of the postings regarding bidirectional invocation you mentioned and I quote "...A better solution would be to modify the CallbackSenderI::run() method implementation to not lock the mutex while invoking on the callback receiver proxy" . Would you mind giving an example of that?
3. I am having a problem with bidirectional implementation where calls are dispatched to the wrong clients. Well, I have to say that the initial implementation was synchronous and it worked fine. Later we decided to switch to bidirectional mode (due to firewall considerations) and we adjusted the source code accordingly to get Receiver's proxy via Identity, but no further modifications have been made. Are we doing something wrong?

Comments

  • benoit
    benoit Rennes, France
    Hi,
    vit wrote: »
    Hi there,

    I am relatively new to ICE, so please bear with me.
    I checked all your examples located under /demo/ice and I have to state that they are couple of things I am still not clear about.
    They are as follows:
    1. In case of a bidirectional invocation, you implement servant as a Thread (run() method is provided), but in case of a Callback such thread does not exist. Why?

    There are no particular reasons other than it's not the same demo :).

    There are no technical reasons that would prevent implementing the callback demo using bi-directional connections (and without a special callback sender thread).
    2. In one of the postings regarding bidirectional invocation you mentioned and I quote "...A better solution would be to modify the CallbackSenderI::run() method implementation to not lock the mutex while invoking on the callback receiver proxy" . Would you mind giving an example of that?

    The demo used to invoke callback() on the callback receiver with the mutex locked. The demo was fixed since to invoke on the callback receivers with the mutex released, this way other threads can lock the mutex while the sender thread invokes on the callback receivers.
    3. I am having a problem with bidirectional implementation where calls are dispatched to the wrong clients. Well, I have to say that the initial implementation was synchronous and it worked fine. Later we decided to switch to bidirectional mode (due to firewall considerations) and we adjusted the source code accordingly to get Receiver's proxy via Identity, but no further modifications have been made. Are we doing something wrong?

    It's hard to say without more information. How do you create the proxies for the client callback objects? Could you perhaps post the client and server code? I'll be happy to take a look to see what could be wrong.

    Cheers,
    Benoit.
  • Here's the server method that gets invoked from the client:
    public void remoteCommand( Ice.Identity __identity,
    String __command,
    Ice.Current __current) throws DakotaException {

    _logger.info( "Received command: " + __command );

    CallbackReceiverPrx callbackreceiverprx;
    Ice.ObjectPrx proxy = null;

    try {

    proxy = __current.con.createProxy( __identity );
    callbackreceiverprx = CallbackReceiverPrxHelper.uncheckedCast( proxy );
    String line;

    _process = Runtime.getRuntime().exec( __command );
    System.out.println("Executing " + __command );
    _input = new BufferedReader( new InputStreamReader( _process.getInputStream()) );
    String str = null;
    while (( str = _input.readLine()) != null) {
    callbackreceiverprx.stdout( str + '\n');
    }
    _logger.info("Process exit value: " + _process.waitFor() );


    } catch (Exception e) {

    System.out.println("ERROR: " + e.getMessage() );
    System.out.println("Caused by: " + Arrays.toString( proxy.ice_getEndpoints() ));
    e.printStackTrace();
    throw setException( e.getMessage() );

    } finally {
    try {
    callbackreceiverprx = null;
    cleanOut( _process, _input);
    }catch( Exception ignored ){}

    }
    }
  • You said "...The demo was fixed since to invoke on the callback receivers with the mutex released". Where do I find this new example? Can you point me out?
  • Client's code

    Here's a snapshot from the client:

    Ice.ObjectPrx proxy = communicator.stringToProxy("XXXXXXX:default -h localhost -p 10040");
    CallbackPrx callbackprx = CallbackPrxHelper.checkedCast( proxy );

    Ice.ObjectAdapter adapter = communicator.createObjectAdapter("");
    Ice.Identity ident = new Ice.Identity();
    ident.name = Ice.Util.generateUUID();
    ident.category = "";
    adapter.add(new CallbackReceiverI(), ident);
    adapter.activate();
    callbackprx.ice_getConnection().setAdapter(adapter);
    callbackprx.remoteUnixCommandBidir(ident, _command );
    communicator.waitForShutdown();
  • benoit
    benoit Rennes, France
    vit wrote: »
    You said "...The demo was fixed since to invoke on the callback receivers with the mutex released". Where do I find this new example? Can you point me out?

    Checkout the demo/Ice/bidir demo from your Ice 3.2.1 release. The implementation of the CallbackSenderI.run method calls the callback() method outside the synchronization block. This wasn't the case in some previous releases.

    Your code looks fine. You could replace the following:
    Ice.Identity ident = new Ice.Identity();
    ident.name = Ice.Util.generateUUID();
    ident.category = "";
    adapter.add(new CallbackReceiverI(), ident);
    

    with:
    adapter.addWithUUID(new CallbackReceiverI());
    

    Also, proxy.ice_getEndpoints() on a "fixed" proxy (i.e.: a proxy created using Ice.Connection.createProxy()) always returns an empty array of endpoints (a fixed proxy doesn't have any endpoints, it's bound to the incoming connection).

    In any case, I don't see how callbacks to clients could get mixed up. Your client is calling remoteUnixCommandBidir but you only provided the implementation of remoteCommand, is this the same method? To investigate this, I suggest that you add an "Ice.Identity" parameter to your stdout() method and pass back the receiver identity to print it in the callback receiver implementation and ensure it's correct.

    Cheers,
    Benoit.
  • matthew
    matthew NL, Canada
    As a further note the best, and often fastest way, to get help on an issue is to post a complete self-contained compilable example that demonstrates your problem. Posting code-snippets, in 99% of cases, doesn't help track down the problem.
  • Hello,

    I am also interested in folding some sort of callback hooks into a server, however, am finding the bidir demo somewhat simplistic.

    If we start from CallbackReceiver interfaces on the client side (implementation I assume) and CallbackSender (server-side hooks connecting callback identity), how might we go about folding that into an existing servant ecosystem?

    Just implement CallbackSender along with other interfaces? Something like this:

    class TestServant implements ITestServant, CallbackSender {/*...*/};

    Also, I am test driving using C# for demo purposes, eventually we'll want C++, but for simplicity sake, C# now. How do we tell slice2cs where to find Ice/Identity.cs? For instance, slice2cpp appears to have the include-dir whereas slice2cs does not.

    Edit: This may be to do with no VS2012 addins too? Such as the addins help resolve any internal Ice includes. Which would be fine, as long as we can land in at least VS2010 / .NET 4 for some of its features and be somewhat current.

    Thank you.

    Regards,

    Michael
  • benoit
    benoit Rennes, France
    You don't necessarily have to re-use the CallbackSender interface, you could just add a new "void addClient(CallbackReceiver* prx);" method to your ITestServant Slice interface. Another option is to modify your ITestServant Slice interface to also extend the CallbackSender interface and keep the CallbackSender interface. It's up to you to decide which is best, in this respect the Slice language is very similar to object oriented languages.

    I assume you meant Identity.ice instead of Identity.cs. The slice compilers all support the -I<include dir> option to specify include directories (e.g.: slice2cs -I/opt/Ice-3.4.2/slice Test.ice).

    If you use the Visual Studio Add-In, you don't have anything to do, the path for the Ice slice files gets added automatically. I recommend checking out the Ice demos projects if you want to see how things are typically setup.

    Cheers,
    Benoit.
  • Need some clarification concerning the whole "bidirectional versus callback" topic. Reading some further documentation, seems the issue is more to do with firewalls than anything else.

    For instance, we would incorporate the callback hooks if firewalls were an issue. But if they weren't, then we could simply have the client start up separate servants of its own and server could connect to those as "ordinary" clients? With a caveat being that we probably want the callback servants running in a thread as per the callback examples?

    Thank you...
  • benoit
    benoit Rennes, France
    Yes, bi-directional connections are useful when the client can't accept network connections. In this case, the server re-uses the Ice connection that was established by the client to send invocations back to the client.

    If your client can accept network connections, they can instead also indeed act as "servers" and create an object adapter that listens on an endpoint like regular Ice servers. The demo/Ice/callback demo demonstrates this scenario.

    Cheers,
    Benoit.
  • Okay, so I am beginning to grasp the nature of callbacks. Like a server you need the object adapter to which you add the callback receiver and activate the adapter. Not sure I understand the need for a receiver proxy though. How do we have client conversing with server through primary (non-callback) interfaces, and have a callback instance waiting for response? Again, the examples are a good starting point, bidir and callback, but a bit simplistic for even a minimal a real-world scenario. If you please. Thank you.
  • benoit
    benoit Rennes, France
    I'm afraid it's not clear to me what you're looking for. Why do you need a callback to wait for the response?

    A synchronous invocation on a proxy will wait until the server sends the response and provide the return and out parameters.

    You can also use AMI to make an asynchronous invocation. Instead of waiting, the call on the proxy will return immediately. The client will be notified asynchronously through a local callback object (or function/delegate depending on the language mapping) when the response is available (the return and out parameters are provided to the callback).

    For long running invocations, another design is to do something like the callback demo. The client invokes on the server to initiate the computing and provides a proxy to a callback Ice object (the callback receiver in the callback demo). When the server is done with the computing, it sends the result by invoking the callback Ice object provided by the client.

    Cheers,
    Benoit.
  • Let's see if some code snippets help. But for these questions, the client/server is pretty much the same approach as the simple demo one inheriting from Ice.Application.

    I want to loosely couple the server servant and callback issues, at least as far as the client is concerned. So, to do this, prior to establishing connection with the server servant through client proxy, I can register a callback through a client side adapter? Could potentially, probably would, have a completely separate TCP configuration from other servant and proxies.
    private void startCallback()
    {
      //TODO: instance dissappears into the Communicator ObjectAdapter ether?
      Ice.ObjectAdapter adapter = communicator().createObjectAdapter("TestCallbackReceiver");
      adapter.add(new TestCallbackReceiverI(), communicator().stringToIdentity("testCallback"));
      adapter.activate();
    }
    

    No need to wait for any communicators to shutdown or anything like that, right? Any special way to set it up, as you said, for asynchronous communication (probably preferred)? That's with the with the ["AMI"] issue?

    Then on the server side, I setup a callback servant proxy of its own? This is started by a thread and runs until an AutoResetEvent is set, for instance, at which point the callbacks cease.
    var prx = communicator().propertyToProxy("TestCallbackReceiver.Proxy")
      .ice_twoway().ice_timeout(-1).ice_secure(false);
    var callbackReceiver = ITestCallbackReceiverPrxHelper.checkedCast(prx);
    if (callbackReceiver == null)
    {
      Console.Error.WriteLine("invalid proxy");
      return;
    }
    while (!_someEvent.WaitOne(250))
    {
      //And so on.
      callbackReceiver.doSomethingInteresting(1, 2, 3);
    }
    
  • So sweet! That's exactly what I did and but for a mis-named callback proxy in the server's config file, works beatifully. Then it's all about application SOLID design, protecting shared memory among loosely coupled application concerns, etc. Thank you so much!