Creating New Tasks

A new task can be created by creating a new Task object, and passing a resolver for the new task to Task's constructor:

Task t = new Task(resolver);

Each new task is completely empty unless you explicitly put something in it:

We apologize for the inconvenience of this; we're working on creating a standard way of launching tasks that is less Spartan than this.  (For the moment, if you have to create several tasks, consider using the subsystem manager in the jos.manager package to launch them.)

The first thing we need to take care of is building a resolver for the new task.  We've seen how to get bytecode for new classes from the file system using a FileResolver.  But where are the built-in classes like java.lang.Object and java.lang.String located?  Main sets up several resolvers where different classes can be found.  It places these resolvers in a StartData data structure, which can be found in the static field Start.data in the Hello task.  Some of these resolvers are:

In general, one or more of these resolvers is enough to get a simple task going, although you may want to add more resolvers in order to fetch bytecode, say, from across the network.  Let's look at what some of the different resolvers do in practice:

// Hello.java
import cornell.slk.jkernel.std.Start;

public class Hello
{
    public static void main(String[] args)
        throws Exception
    {
        System.out.println(
            Start.data.mainResolver.resolveClassName(
                "Hello"));
        System.out.println(
            Start.data.standardResolver.resolveClassName(
                "Hello"));
        System.out.println(
            Start.data.standardResolver.resolveClassName(
                "java.lang.Object"));
        System.out.println(
            Start.data.standardResolver.resolveClassName(
                "java.lang.System"));
    }
}

The output of this is:

[B@1f1c4d
null
cornell.slk.jkernel.core.SharedClass@1f1c2f
[B@1f1b6f

Let's go through this line by line.

Now that we have some classes for the new task to use, we need to put some objects in the task.  This is done by seeding the new task with an initial object:

Task t = new Task(resolver);
t.seed("someclass");

Here "someclass" is the name of a class which should be instantiated in the new task (this class must have a public constructor taking no arguments).   Naturally, the new task's resolver must be able to find this class, and other classes that the class refers to.

There's something still missing in this example.  Now the new task has an object, and a set of classes, but it is completely isolated from the rest of the world.  We need a way to for the parent task and its new child task to communicate with each other.  We'll fix this by using the seed object as a communication channel between the old and new tasks.  In the J-Kernel, all cross-task communication is done through capabilities, which are special kinds of objects that can be passed from one task to another.   Therefore, the J-Kernel creates a capability for the seed object and returns it to the parent task:

Task t = new Task(resolver);
Capability c = t.seed("someclass");

In order for this to work, the seed object needs to have some special properties.   It must implement one or more remote interfaces, where a remote interface is any interface that extends the class Remote.  Let's create a remote interface, and a class which implements this remote interface:

// Counter.java
// Counter remote interface:
import cornell.slk.jkernel.core.Remote;
import cornell.slk.jkernel.core.RemoteException;
public interface Counter extends Remote
{
    int incrementCount() throws RemoteException;
}

// CounterImpl.java
// Sample counter implementation
public class CounterImpl implements Counter
{
    private int i = 0;
    public int incrementCount() {return ++i;}
}

Now the parent task can do something like:

Task t = new Task(resolver);
Capability c = t.seed("CounterImpl");

to get a capability for the CounterImpl seed object.

A capability for an object implements all the remote interfaces that the original object implemented.  So the capability c implements the remote interface Counter and can thus be cast to Counter:

Task t = new Task(resolver);
Counter c = (Counter) t.seed("CounterImpl");

However, remember that there can be more than one Counter class in the system (just as there may be more than one java.lang.System class).  If the parent and child tasks each have their own version of Counter, then the cast shown above will fail!  This can easily happen.  If both tasks have a resolver that returns bytecode for the class Counter, then each task will use this bytecode to instantiate its own version of Counter.

In order for the cast shown above to succeed, the two tasks need to coordinate so that they share the same Counter class.  Here's the plan:

The parent task can use the function Task.shareClass to create a SharedClass object.  We should point out some of the J-Kernel's rules about class sharing here.   A shared class cannot have static fields, because these fields could be used as unauthorized communication channels for cross-task communication (remember, cross-task communication in the J-Kernel is supposed to happen through capabilities).   Furthermore, any class referred to by a shared class must also be shared.

To get a resolver that returns the SharedClass object for Counter, it is convenient to use the TableResolver class from jkernel.util, which is keeps a hash table that maps names to bytecode or names to SharedClasses.

Once the capability has been cast to a Counter, we can perform cross-task calls by making method invocations on the capability.  In this example, the only method implemented by Counter is incrementCount, so cross-task calls are made by invoking this method.

So now we can put the whole thing together.  Create three files:

// Hello.java
import cornell.slk.jkernel.core.Task;
import cornell.slk.jkernel.core.SharedClass;
import cornell.slk.jkernel.core.Resolver;
import cornell.slk.jkernel.std.Start;
import cornell.slk.jkernel.util.TableResolver;
import cornell.slk.jkernel.util.CompoundResolver;

public class Hello
{
    public static void main(String[] args)
        throws Exception
    {
        System.out.println("Hello.");

        // Share the class Counter, and create a resolver that
        // returns the SharedClass object for Counter:
        SharedClass counterClass = Task.shareClass("Counter");
        TableResolver counterClassResolver = new TableResolver();
        counterClassResolver.setMapping("Counter", counterClass);

        // Create a resolver for the new task:
        Resolver childResolver = new CompoundResolver(
            new Resolver[] {
                Start.data.standardResolver,
                counterClassResolver,
                Start.data.mainResolver
            });

        // Create the child task:
        Task child = new Task(childResolver);

        // Seed the child task with a CounterImpl object,
        // and get the capability for this object:
        Counter counter = (Counter) child.seed("CounterImpl");

        // Now we can use the counter to make calls into the
        // child task:
        System.out.println(counter.incrementCount());
        System.out.println(counter.incrementCount());
        System.out.println(counter.incrementCount());
    }
}

One other useful resolver is a CompoundResolver, which holds an array of resolvers and queries them in order when it receives a request.  In the Hello program, a CompoundResolver is used to create a resolver for the child task which:

When the Hello program is run, it should produce the following:

Hello.
1
2
3

Next: More about tasks and capabilities