-
Notifications
You must be signed in to change notification settings - Fork 1
Running a standalone Java program in the Minha platform using the API is as simple as:
World world = new Simulation();
Host host = world.createHost();
Process proc = host.createProcess();
Entry<Main> e = proc.createEntry();
e.call().main("test.Main", "arg0", "arg1");
world.close();
The steps involved are:
-
Creating a simulation container;
-
Creating a simulated host within that container;
-
Creating a simulated process within that host;
-
Creating an entry proxy for a main class loader;
-
Invoking the main method with a synchronous call, that implicitly runs the simulation until the program terminates.
-
Cleaning up, by closing the simulation container. Otherwise, a simulation, even while not running, will keep stopped threads preventing garbage collection of state.
This API allows invocation of arbitrary methods, several scheduling options, asynchronous invocations, and callbacks, described in the following sections.
In addition to running complete Java programs, i.e., classes with a proper main method, Minha allows running incomplete programs by invoking methods in arbitrary interfaces within the simulation container by external driver code.
Consider the following interface and implementation:
@Global
public interface MyInterface {
public int myMethod(int i);
}
public class MyImpl implements MyInterface {
public int myMethod(int i) {
return i+1;
}
}
Note the following restrictions:
-
The interface must be declared global, such that it is shared between the simulation container and driver program;
-
The implementation must not be global, otherwise, execution can not be virtualized by the simulation container;
-
Parameters and return results should be global and not make use of any synchronization primitives (to be on the safe side, it is better to only use primitive types, wrapped primitives, strings, and arrays);
-
The implementation itself can (and should) make use of any classes and APIs.
Such interface and implementation can then be used as follows:
Host host = world.createHost();
Process proc = host.createProcess();
Entry<MyInterface> e = proc.createEntry(MyInterface.class, MyImpl.class. getName());
int i = e.call().myMethod(1);
System.out.println("1 + 1 ="+i);
i = e.call().myMethod(10);
System.out.println("10 + 1 ="+i);
world.close();
Each Entry<...> object is implicitly associated with a simulated thread. Successive calls are thus processed by this thread within the simulation. Each invocation advances the simulation just enough for the result to be available.
By default, invocations are scheduled at the current simulation time. When a container is created, this is 0. Further invocations depend on how much simulation has progressed so far. The driver program can however set the proxy on three different modes:
-
The default mode, in which the invocation is schedule as soon as possible, is set by:
e.asap();
-
The relative mode, in which the invocation starts after some simulation time has elapsed is set by
after(...). For instance:e.after(1, SECONDS);
-
The absolute mode, in which the invocation starts at some simulation time is set by
at(...). For instance:e.at(10, SECONDS);
These modes will persist after being set for all future invocations. If relative or absolute modes result in trying to schedule invocations in the simulation past, they default back to asap() mode. These invocations can also be chained as follows:
e.at(10, SECONDS).call().m1(); // 10 seconds into the simulation
e.after(1, SECONDS).call().m2(); //1 second after m1() ended
e.asap().call().m3(); // imedially after m2() ends
= Asynchronous Invocations =
A simulation container is not thread safe and should not be reentered through any of the associated objects, i.e., the World, Host's, or Entry<...>'s. Scheduling concurrent entry invocations within the simulation container can only be achieved with asynchronous invocations. This is as simple as using queue() instead of call():
World world = new Simulation();
Host host = world.createHost();
Process proc = host.createProcess();
Entry<Main> e1 = proc.createEntry(...);
Entry<Main> e1 = proc.createEntry(...);
e1.at(10, SECONDS).queue().m1();
e2.at(5, SECONDS).queue().m2();
world.runAll(e1, e2);
int i = e1.getResult();
Note that:
-
Scheduling asynchronous invocations does not implicitly advance the simulation and an explicit invocation to run the the simulation is required;
-
A separate entry object is needed for each invocation that will be waited on and the simulation will advance until all of them have completed;
-
The result from the invocation can be collected with
getResult()and is available after the simulation has been advanced sufficiently for the corresponding invocation to conclude; -
Exceptions thrown by asynchronous invocations are also collected and re-thrown by
getResult(), so it should be called even withvoidmethods.
Synchronous and asynchronous invocations can be freely mixed. Note however that each entry object will only keep state for one invocation. Moreover, an entry is single threaded. So if multiple asynchronous invocations are scheduled on the same proxy, the first can potentially delay the others, or even prevent them from running at all if it does not return.
Besides driving the simulation by scheduling invocations, Java code can also get information about progress, and influence such progress, by handling callbacks. Consider the following classes:
@Global
public interface MyInterface {
public void myMethod(int i, MyCallback cb);
}
@Global
public interface MyCallback {
public void callback(int i);
}
public class MyImpl implements MyInterface {
public void myMethod(int i, MyCallback cb) {
cb.callback(i+1);
}
}
Note the following restrictions:
-
The callback interface must be declared global, too, such that it is shared between the simulation container and driver program;
-
Marking of the callback implementation is irrelevant since it is never enters the simulation container, for instance, it can be a mock object used to validate an invocation sequence.
Such interface and implementation can then be used as follows:
World world = new Simulation();
Host host = world.createHost();
Process proc = host.createProcess();
Entry<MyInterface> e = proc.createEntry(MyInterface.class, MyImpl.class. getName());
Exit<MyInterface> x = proc.createExit(MyCallabck.class, new MyCallback() {
public void callback(int i);
System.out.println("i ="+i);
}
});
e.call().myMethod(1, x.report());
world.close();
Callbacks obey the following rules:
-
A simulation container executes all callbacks on a single thread, so they should not ever block, namely by invoking
Entry<...>.call(); -
Asynchronous callbacks, as produced with
Exit<...>.report(), cannot reenter the simulation container that is not stopped while they run. -
Callbacks are not necessarily executed in increasing simulated time order, but respect causal order in the simulation;
-
When the simulation halts, after a synchronous entry or an explicit run, all causally preceding callbacks have already been processed.
Callbacks can also be invoked synchronously:
World world = new Simulationd();
Host host = world.createHost();
Entry<MyInterface> e1 = proc.createEntry(MyInterface.class, MyImpl.class. getName());
final Entry<MyInterface> e2 = Proc.createEntry(...);
Exit<MyInterface> x = proc.createExit(MyCallabck.class, new MyCallback() {
public int callback(int i);
e2.after(5, SECONDS).queue().something();
return i+1;
}
});
e.call().myMethod(1, x.callback());
world.close();
Synchronous callbacks have the following properties:
-
Simulation is stopped while they run, so driver code can examine simulation state in a consistent snapshot;
-
Further invocations can be scheduled asynchronous using entry objects;
-
A return value, or exception, is conveyed to the simulation.
Finally, simulated CPU overhead and round-trip delays can be set on the exit object. These delays are imposed on the simulated thread that performs the invocation. These delays can be modified by the callback itself, thus adjusting the round-trip and overhead according to parameter values.
Creating a number of identical entry objects, each for a different process in its own host in a distributed system can easily be accomplished with utility methods in the container. For instance, 10 nodes all executing the same:
World world = new Simulation();
Entry<Main>[] e = world.createEntries(10);
for(int i=0; i<e.length; i++)
e.queue().main("test.Main", "arg0", "arg1");
world.runAll(e);
world.close();
There are utility methods to create entries for user defined interfaces and for exit objects too.