Asynchronous invocation example

This example is amazingly similar to the node facet example (Hello2Client etc, Chapter [*]) but we'll use a new facet for which threading is unavoidable.

Here is the facet interface we'll use:


package org.jtrix.project.documentation.threading;

import org.jtrix.base.*;

/** Facet interface for holding a secret.
 */
public interface ISecretHolderFacet extends IRemote
{
    /** Get the secret. However, this will block until unblock() is called.
     */
    public String getSecret();

    /** Free the secret. After this is called, getSecret() will return happily.
     */
    public void unblock();
}

The plan is to call getSecret() followed by unblock(). But clearly we have to work asynchronously, otherwise we'll never get to that second call. (Of course, if we call unblock() first then we can do without this kind of threading, but that would defeat the point of this example.)

Here's an ordinary Java implementation of the facet:


package org.jtrix.project.documentation.threading;

import org.jtrix.base.*;

/** Implementation of ISecretHolderFacet.
 */
public class SecretHolder implements ISecretHolderFacet
{
    private static final String _SECRET = "Send three and fourpence";
    private boolean _is_blocked = true;

    public synchronized String getSecret()
    {
        while (_is_blocked)
        {
            try
            {
                wait();
            }
            catch (InterruptedException e)
            {
                return "Interrupted while waiting for secret";
            }
        }

        return _SECRET;
    }

    public synchronized void unblock()
    {
        _is_blocked = false;
        notify();
    }
}

This is nothing special as far as threaded code goes.

Here's another very ordinary piece of code. It's just a netlet which makes the facet available as a node facet. It's almost identical to Hello2Provider from Chapter [*]:


package org.jtrix.project.documentation.threading;

import org.jtrix.base.*;
import org.jtrix.project.nodality.facet.INodeAdminFacet;
import org.jtrix.project.nodality.facet.INodeFacetCollection;
import org.jtrix.project.nodality.facet.NetletID;

/** Offer a SecretHolder as a node facet.
 */
public class SecretProvider implements INetlet
{
    public byte[] initialise(INode node, Object bean, byte[] unsigned)
        throws InitialiseException
    {
        try
        {
            String na_name = INodeAdminFacet.class.getName();
            INodeAdminFacet na = (INodeAdminFacet)node.bindFacet(na_name);
            na.addNodeFacets(new FacetProvider(), null);
        }
        catch (Exception e)
        {
            System.out.println("Netlet failed to start");
            e.printStackTrace();
            throw new InitialiseException(e.toString());
        }

        System.out.println("Netlet started");
        return null;
    }

    public void terminate(long date, IShutdownProgress progress)
    {
        // Nothing to clean up when we terminate
    }

    public IService bindService(Warrant warrant, IService consumer)
        throws ServiceBindException
    {
        throw new ServiceBindException();
    }

    public String[] getFacets()
    {
        return new String[0];
    }

    public IRemote bindFacet(String facet) throws FacetBindException
    {
        throw new FacetBindException();
    }

    /** Provides a secret holder facet to the node.
     */
    private class FacetProvider implements INodeFacetCollection
    {
        private final String _facet_name = ISecretHolderFacet.class.getName();

        /** Say what facets we offer.
         */
        public String[] getFacets()
        {
            return new String[]{ _facet_name };
        }

        /** Bind the named node facet, as requested by a particular netlet.
         */
        public IRemote bindNodeFacet(NetletID id, String facet)
            throws FacetBindException
        {
            System.out.println("Instantiating "+facet);

            if(!facet.equals(_facet_name))
            {
                throw new FacetBindException();
            }

            System.out.println("Facet instantiated");

            return new FacetHandle(new SecretHolder(), facet);
        }

    } // FacetProvider

} // SecretProvider

So now the stage is set. We have a netlet which advertises a facet to the node, and we're going to have to invoke its methods asynchronously.

Here's the start of the client netlet. All the exciting work is deferred til later, in method demoAsync(). All this does so far is bind the facet and provide the other aspects of a simple netlet:


package org.jtrix.project.documentation.threading;

import org.jtrix.base.*;
import java.lang.reflect.*;

/** Invoke a facet's methods asynchronously.
 */
public class SecretClient implements INetlet
{
    public byte[] initialise(INode node, Object bean, byte[] unsigned)
        throws InitialiseException
    {
        try
        {
            String facet_name = ISecretHolderFacet.class.getName();
            IRemote proxy = (IRemote)(node.bindFacet(facet_name));
            demoAsyncClient(proxy);
        }
        catch(Exception e)
        {
            System.out.println("Sorry, couldn't demonstrate async clients.");
            e.printStackTrace();
            throw new InitialiseException(e.toString());
        }

        return null;
    }

    public void terminate(long date, IShutdownProgress progress)
    {
        // No clean-up necessary
    }

    public IService bindService(Warrant warrant, IService consumer)
        throws ServiceBindException
    {
        throw new ServiceBindException();
    }

    public String[] getFacets()
    {
        return new String[0];
    }

    public IRemote bindFacet(String facet) throws FacetBindException
    {
        throw new FacetBindException();
    }

Here's the guts of it. Let's have a look first and discuss it after:


    /** Demonstrate how to take a mediated proxy and invoke it asynchronously.
     * @param proxy  Any mediated proxy, such as return by a bindFacet() call.
     */
    private void demoAsyncClient(IRemote proxy) throws Exception
    {
        // Turn the proxy into an asynchronous client and call it asynchronously

        IAsynchronous.IClient async_facet = (IAsynchronous.IClient)proxy;
        Method meth = ISecretHolderFacet.class.getMethod("getSecret", new Class[0]);
        async_facet.invoke(meth, new Class[0], new ReturnHandler());

        // Now take the same proxy and call it normally.

        ISecretHolderFacet normal_facet = (ISecretHolderFacet)proxy;
        normal_facet.unblock();
    }

The method takes the mediated proxy as a parameter. That is, it deals with the result of a bindFacet() call, which is the proxy given to us by the mediator. Normally we just cast this IRemote object straight into the type for the desired facet (such as IHelloFacet, ISecretHolderFacet and so on). But here we do something else first.

We can see there are two parts to the method: using the facet asynchronously, and using it normally. Let's focus on the asynchronous use first.

The cast on the first line gives us an asynchronous client, so we can invoke the getSecret() method appropriately. Next we get a java.lang.reflect.Method which simply identifies that method. Then we invoke it. We tell our asynchronous client the method we want to invoke, what parameters we're sending to the method (none) and a handler to deal with the result. We'll deal with the handler shortly.

So what we've done is invoke the getSecret() method asynchronously. That method won't return yet because we haven't called unblock(), but we carry on our execution regardless.

We're now in the second part. Here we simply cast the IRemote proxy into the appropriate class and unblock the secret. That's all we need to do.

Now here's the handler to deal with the return value of getSecret():


    private class ReturnHandler implements IAsynchronous.IListener
    {
        /** Handle a successful return from the method invocation.
         */
        public void completed(Object rtn_value)
        {
            System.out.println("The secret is: "+(String)rtn_value);
        }

        /** Handle an exception or other throwable from the method invocation.
         */
        public void failed(Throwable t)
        {
            System.out.println("Sorry, couldn't get the secret.");
            t.printStackTrace();
        }
    }

} // SecretClient

We can see that this class needs to be an IAsynchronous.IListener to handle the return from an asynchronous call. We implement its two methods.

The completed() method handles a successful result--the method call returns okay. We handle it by simply taking the result value and outputting it. Since getSecret() returns a String we know we can safely cast it into a String and output it.

On the other hand our asynchronous call might have thrown an exception. This is passed to failure() which in our case simply reports the problem.

Note that this handler will of course start in a new thread. The remote getSecret() will return in the facet's implementation (in SecretHolder) and its return value gets passed back to us via the mediator and the node starts the thread in which completed() is run. And that also means (looking back at the last section on managing netlet concurrency) that we need to have our concurrency level set high enough, otherwise the node will wait until there is a thread free. Ordinarily this is not an issue--if we don't explicitly set our concurrency level then the node is happy to start all the threads it needs. But if we have previously called setConcurrency() then we need to be aware of this.

And that's the end of the class. To recap, we have a client netlet which binds a facet and invokes two methods, one of them asynchronously. It has a handler deals with the result of the asynchronous method call.

Nik Silver 2002-03-09