Active objects are created on a per-object basis: an application can
contain active as well as passive instances of a given class. In the
remaining part of this section, we will consider that we want to create an
active instance of class example.A
. Although almost any
object can be turned into an Active Object, there are some restrictions that
will be detailed below.
Any method call m done on a given instance
a of A
would result in the
invocation of the method m on a by
the caller thread. By contrast, the same call done on the active object
aa created from A
would result into
placing a request embedding the method call for m in
the request queue of the active object
aa. Then, later on, the active thread of
aa would eventually pick-up and serve the request for
the method m. That would result in the invocation of
m on the reified object a by the
active thread.
The code for creating a passive instance of A
could
be:
A a = new A(26, "astring");
In ProActive there are two ways to
create active objects. One way is to use
ProActive.newActive
and is based on the instantiation of
a new object, the other is to use ProActive.turnActive
and is based on the use of an existing object.
When using instantiation-based creation, any argument passed to the
constructor of the reified object through
ProActive.newActive
is serialized and passed by copy to
the object. That's because the model behind ProActive is uniform whether the active object is
instantiated locally or remotely. The parameters are therefore guaranteed
to be passed by copy to the constructor. When using
ProActive.newActive
, one needs to make sure that the
constructor arguments are Serializable
. On the other
hand, the class used to create the active object does not need to be Serializable
even in the case of remotely-created Active Objects. Bear in mind also
that a reified object must have a declared empty no-args constructor in
order to be properly created.
A a; Object[] params = new Object[] { new Integer (26), "astring" }; try { a = (A) ProActive.newActive("example.A", params); } catch (ActiveObjectCreationException e) { // creation of ActiveObject failed e.printStackTrace(); } catch(NodeException ex){ ex.printStackTrace(); }
This code creates an active object of class A
in
the local JVM. If the invocation of the constructor of class
A
throws an exception, it is placed inside an exception
of type ActiveObjectCreationException
. When the call to
newActive
returns, the active object has been created
and its active thread is started.
The first parameter of newActive is a string containing the fully-qualified name of the class we want to make active. Parameters to the constructor have to be passed as an array of Object. Then, according to the type of the elements of this array, the ProActive runtime determines which constructor of class A to call. Nevertheless, there is still room for some ambiguity in resolving the constructor because:
As the arguments of the constructor are stored in an array of
type Object[]
, primitive types have to be
represented by their wrappers object type. In the example above, we
use an Integer
object to wrap the
int
value 26. An ambiguity then arises if two
constructor of the same class only differ by converting a primitive
type to its corresponding wrapper class. In the example below, an
ambiguity exists between the first and the second
constructors.
If one argument is null, the runtime can obviously not
determine its type. This is the second source of ambiguity. In the
example below, an ambiguity exists between the third and the fourth
constructors if the second element of the array is
null
.
public A (int i) { // } public A (Integer i) { // } public A (int i, String s) { // } public A (int i, Vector v) { // }
It is possible to pass a third parameter to the call to
newActive
in order to create the new active object on
a specific JVM, possibly remote. The JVM is identified using a
Node
object that offers the minimum services
ProActive needs on a given JVM to communicate with this JVM. If that
parameter is not given, the active object is created in the current JVM
and is attached to a default Node.
A node is identified by a node URL which is formed using the
protocol, the hostname hosting the JVM where is the node located and the
name of the node. The NodeFactory
allows to create or
lookup nodes. The method newActive
can take in
parameter a nodeURL as a String
or a
Node
object that points to an existing node. Here an
example:
a = (A) ProActive.newActive("example.A", params, "rmi://pluto.inria.fr/aNode"); or Node node = NodeFactory.getNode("rmi://pluto.inria.fr/aNode"); a = (A) ProActive.newActive("example.A", params, node);
Object-based creation is used for turning an existing passive object instance into an active one. It has been introduced in ProActive as an answer to the following problem. Consider, for example, that an instance of class A is created inside a library and returned as the result of a method call. As a consequence, we do not have access to the source code where the object is created, which prevents us for modifying it for creating an active instance of A. Even if it were possible, it may not be likely since we do not want to get an active instance of A for every call on this method.
When using object based creation, you create the object that is
going to be reified as an active object before hand. Therefore there is no
serialization involved when you create the object. When you invoke
ProActive.turnActive
on the object two cases are
possible. If you create the active object locally (on a local node), it
will not be serialized. If you create the active object remotely (on a
remote node), the reified object will be serialized. Therefore, if the
turnActive
is done on a remote node, the class used to
create the active object this way has to
beSerializable
. In addition, when using
turnActive
, care must be taken that no other references
to the originating object are kept by other objects after the call to
turnActive. A direct call to a method of the originating object without
passing by a ProActive stub on this object will break the model.
Code for object-based creation looks like this:
A a = new A (26, "astring"); a = (A) ProActive.turnActive(a);
As for newActive
, the second parameter of
turnActive
if given is the location of the active
object to be created. No parameter or null
means that
the active object is created locally in the current node.
When using this method, the programmer has to make sure that no other reference on the passive object a exist after the call to turnActive. If such references were used for calling methods directly on the passive A (without going through its body), the model would no more be consistent and specialization of synchronization could no more be guaranteed.
Customizing the activity of the active object is at the core of ProActive because it allows to specify fully the behavior of an active object. By default, an object turned into an active object serves its incoming requests in a FIFO manner. In order to specify another policy for serving the requests or to specify any other behaviors one can implement interfaces defining methods that will be automatically called by ProActive.
It is possible to specify what to do before the activity starts, what the activity is and what to do after it ends. The three steps are:
the initialization of the activity (done only once)
the activity itself
the end of the activity (done only once)
Three interfaces are used to define and implement each step:
InitActive (see code in InitActive.java)
RunActive (see code in RunActive.java)
EndActive (see code in EndActive.java)
In case of a migration, an active object stops and restarts its activity automatically without invoking the init or ending phases. Only the activity itself is restarted.
Two ways are possible to define each of the three phases of an active object.
Implementing one or more of the three interfaces directly in the class used to create the active object
Passing an object implementing one or more of the three
interfaces in parameter to the method newActive
or
turnActive
(parameter active in those
methods)
Note that the methods defined by those 3 interfaces are guaranted to be called by the active thread of the active object.
The algorithms that decide for each phase what to do are the
following (activity
is the eventual object passed as
a parameter to newActive
or
turnActive
):
InitActive
if activity is non null and implements InitActive we invoke the method initActivity defined in the object activity else if the class of the reified object implements InitActive we invoke the method initActivity of the reified object else we don't do any initialization
RunActive
if activity is non null and implements RunActive we invoke the method runActivity defined in the object activity else if the class of the reified object implements RunActive we invoke the method runActivity of the reified object else we run the standard FIFO activity
EndActive
if activity is non null and implements EndActive we invoke the method endActivity defined in the object activity else if the class of the reified object implements EndActive we invoke the method endActivity of the reified object else we don't do any cleanup
Implementing the interfaces directly in the class used to create
the active object is the easiest solution when you control the class
that you make active. Depending on which phase in the life of the active
object you want to customize, you implement the corresponding interface
(one or more) amongst InitActive
,
RunActive
and EndActive
. Here is
an example that has a custom initialization and activity.
import org.objectweb.proactive.*; public class A implements InitActive, RunActive { private String myName; public String getName() { return myName; } // -- implements InitActive public void initActivity(Body body) { myName = body.getName(); } // -- implements RunActive for serving request in a LIFO fashion public void runActivity(Body body) { Service service = new Service(Body); while (body.isActive()) { service.blockingServeYoungest(); } } public static void main(String[] args) throws Exception { A a = (A) ProActive.newActive("A",null); System.out.println("Name = "+a.getName()); } }
Example 13.1. Custom Init and Run
import org.objectweb.proactive.*; public class Simulation implements RunActive { private boolean stoppedSimulation=false; private boolean startedSimulation=false private boolean suspendedSimulation=false; private boolean notStarted = true; public void startSimulation(){ //Simulation starts notStarted = false; startedSimulation=true; } public void restartSimulation(){ //Simulation is restarted startedSimulation=true; suspendedSimulation=false; } public void suspendSimulation(){ //Simulation is suspended suspendedSimulation=true; startedSimulation = false; } public void stoppedSimulation(){ //Simulation is stopped stoppedSimulation=true; } public void runActivity(Body body) { Service service = new Service(Body); while (body.isActive()) { //If the simulation is not yet started wait until startSimulation method if(notStarted) service.blockingServeOldest(startSimulation()); // If the simulation is started serve request with FIFO if(startedSimulation) service.blockingServeOldest(); // If simulation is suspended wait until restartSimulation method if(suspendedSimulation) service.blockingServeOldest(restartSimulation()); // If simulation is stopped, exit if(stoppedSimulation) exit(); } }
Example 13.2. Start, stop, suspend, restart a simulation algorithm in runActivity method
Even when an AO is busy doing its own work, it can remain reactive to external events (method calls). One just has to program non-blocking services to take into account external inputs.
public class BusyButReactive implements RunActive { public void runActivity(Body body) { Service service = new Service(body); while ( ! hasToTerminate ) { ... // Do some activity on its own ... ... // Non blocking service ... service.serveOldest("changeParameters", "terminate"); ... } } public void changeParameters () {... // change computation parameters} public void terminate (){ hasToTerminate=true;} }
Example 13.3. Reactive Active Object
It also allows one to specify explicit termination of AOs (there is currently no Distributed Garbage Collector). Of course, the reactivity is up to the length of going around the loop.
Passing an object implementing the interfaces when creating the
active object is the solution to use when you do not control the class
that you make active or when you want to write generic activities policy
and reused them with several active objects. Depending on which phase in
the life of the active object you want to customize, you implement the
corresponding interface (one or more) amongst
InitActive
, RunActive
and
EndActive
. Following is an example that has a custom
activity.
Comparing to the solution above where interfaces are directly implemented in the reified class, there is one restriction here: you cannot access the internal state of the reified object. Using an external object should therefore be used when the implementation of the activity is generic enough not to have to access the member variables of the reified object.
import org.objectweb.proactive.*; public class LIFOActivity implements RunActive { // -- implements RunActive for serving request in a LIFO fashion public void runActivity(Body body) { Service service = new Service(Body); while (body.isActive()) { service.blockingServeYoungest(); } } } import org.objectweb.proactive.*; public class A implements InitActive { private String myName; public String getName() { return myName; } // -- implements InitActive public void initActivity(Body body) { myName = body.getName(); } public static void main(String[] args) throws Exception { // newActive(classname, constructor parameter (null = none), // node (null = local), active, MetaObjectFactory (null = default) A a = (A) ProActive.newActive("A", null, null, new LIFOActivity(), null); System.out.println("Name = "+a.getName()); } }
Not all classes can give birth to active objects. There exist some restrictions, most of them caused by the 100% Java compliance, which forbids modifying the Java Virtual Machine or the compiler.
Some of these restrictions work at class-level:
Final classes cannot give birth to active object
Same thing for non-public classes
Classes without a no-argument constructor cannot be reified. This restriction will be softened in a later release of ProActive
Some other happen at the level of a method in a specific class:
Final methods cannot be used at all. Calling a final method on an active object leads to inconsistent behavior.
Calling a non-public method on an active object raises an exception. This restriction disappeared with JDK 1.2.
Creating an active object using ProActive might be a little bit
cumbersome and requires more lines of code that for creating a regular
object. A nice solution to this problem is through the use of the
factory pattern. This mainly applies to
class-based creation. It consists in adding a static method to class
pA
that takes care of instantiating the active object
and returns it. The code is:
public class AA extends A { public static A createActiveA (int i, String s, Node node) { Object[] params = new Object[] {new Integer (i), s}; try { return (A) ProActive.newActive("A", params, node); } catch (Exception e) { System.err.println ("The creation of an active instance of A raised an exception: "+e); return null; } } }
It is up to the programmer to decide whether this method has to throw exceptions or not. We recommend that this method only throws exceptions that appear in the signature of the reified constructor (none here as the constructor of A that we call doesn't throw any exception). But the non functional exceptions induced by the creation of the active object have to be dealt with somewhere in the code.
There are many cases where you may want to customize the body used when creating an active object. For instance, one may want to add some debug messages or some timing behavior when sending or receiving requests. The body is a non changeable object that delegates most of its tasks to helper objects called MetaObjects. Standard MetaObjects are already used by default in ProActive but one can easily replace any of those MetaObjects by a custom one.
We have defined the MetaObjectFactory
interface
(see code in core/body/MetaObjectFactory.java) able to create factories for each of those
MetaObjects. This interface is implemented by
ProActiveMetaObjectFactory
(see code in core/body/ProActiveMetaObjectFactory.java) which provides all the default factories used in
ProActive.
When creating an active object, as we saw above, it is possible to
specify which MetaObjectFactory
to use for that
particular instance of active object being created. The class
ProActive
(see code in ProActive.java) provides extra newActive and turnActive methods
for that:
ProActive.newActive( java.lang.String, java.lang.Object[], org.objectweb.proactive.core.node.Node, org.objectweb.proactive.Active, org.objectweb.proactive.core.body.MetaObjectFactory)
ProActive.turnActive( java.lang.Object, org.objectweb.proactive.core.node.Node, org.objectweb.proactive.Active, org.objectweb.proactive.core.body.MetaObjectFactory)
First you have to write a new MetaObject factory that inherits
from ProActiveMetaObjectFactory
(see code in core/body/ProActiveMetaObjectFactory.java) or directly implements the
MetaObjectFactory
interface (see code in core/body/MetaObjectFactory.java), in
order to redefine everything. Inheriting from
ProActiveMetaObjectFactory
is a great time saver as
you only redefine what you really need to. Here is an example:
public class MyMetaObjectFactory extends ProActiveMetaObjectFactory { private static final MetaObjectFactory instance = new MyMetaObjectFactory(); protected MyMetaObjectFactory() { super(); } public static MetaObjectFactory newInstance() { return instance; } // // -- PROTECTED METHODS ----------------------------------------------- // protected RequestFactory newRequestFactorySingleton() { return new MyRequestFactory(); } // // -- INNER CLASSES ----------------------------------------------- // protected class MyRequestFactory implements RequestFactory, java.io.Serializable { public Request newRequest(MethodCall methodCall, UniversalBody sourceBody, boolean isOneWay, long sequenceID) { return new MyRequest(methodCall, sourceBody, isOneWay, sequenceID, server); } } // end inner class MyRequestFactory }
The factory above simply redefines the
RequestFactory
in order to make the body use a new
type of request. The method protected RequestFactory
newRequestFactorySingleton()
is one convenience method that
ProActiveMetaObjectFactory
(see code in core/body/ProActiveMetaObjectFactory.java) provides to simplify the creation of factories as
singleton. More explanations can be found in the
javadoc
of that class . The use of that factory is fairly simple. All you have
to do is to pass an instance of the factory when creating a new active
object. If we take the same example as before we have:
Object[] params = new Object[] {new Integer (26), "astring"}; try { A a = (A) ProActive.newActive("example.AA", params, null, null, MyMetaObjectFactory.newInstance()); } catch (Exception e) { e.printStackTrace() ; }
In the case of a turnActive
we would
have:
A a = new A(26, "astring"); a = (A) ProActive.turnActive(a, null, null, MyMetaObjectFactory.newInstance());
In this section, we'll have a very close look at what happens when an active object is created. This section aims at providing a better understanding of how the library works and where the restrictions of Proactive come from.
Consider that some code in an instance of class A
creates an active object of class B
using a piece of
code like this:
B b; Object[] params = {<some parameters for the constructor>}; try { // We create an active instance of B on the current node b = (B) ProActive.newActive("B", params); } catch (Exception e) { e.printStackTrace () ; }
If the creation of the active instance of B is successful, the graph of objects is as described in figure below (with arrows denoting references).
The active instance of B is actually composed of 4 objects:
a stub (Stub_B
)
a proxy (BodyProxy
)
a body (Body
)
an instance of B
The role of the class Stub_B
is to reify all
method calls that can be performed through a reference of type
B
, and only these as calling a method declared in a
subclass of B
through downcasting would result in a
runtime error). Reifying a call simply means constructing an object (in
our case, all reified calls are instance of class
MethodCall
) that represents the call, so that it can
be manipulated as any other object. This reified call is then processed
by the other components of the active object in order to achieve the
behavior we expect from an active object.
The idea of using a standard object for representing elements of the language that are not normally objects (such as method calls, constructor calls, references, types,...) is what metaobject programming is all about. The metaobject protocol (MOP) ProActive is built on is described in Chapter 52, MOP: Metaobject Protocol but it is not a prerequisite for understanding and using ProActive.
As one of our objectives is to provide transparent active objects,
references to active objects of class B
need to be of
the same type as references to passive instances of B
(this feature is called polymorphism between
passive and active instances of the same class). This is why, by
construction, Stub_B
is a subclass of class
B
, therefore allowing instances of class
Stub_B
to be assigned to variables of type
B
.
Class Stub_B
redefines each of the methods
inherited from its superclasses. The code of each method of class
Stub_B
actually builds an instance of class
MethodCall
in order to represent the call to this
method. This object is then passed to the BodyProxy
,
which returns an object that is returned as the result of the method
call. From the caller's point of view, everything looks like if the call
had been performed on an instance of B
.
Now that we know how stubs work, we can understand some of the limitations of ProActive:
Obviously, Stub_B
cannot redefine
final
methods inherited from class
B
. Therefore, calls to these methods are not
reified but are executed on the stub, which may lead to
unexplainable behavior if the programmer does not carefully avoid
calling final
methods on active objects.
As there are 6 final methods in the base class
Object
, one may wonder how to live without them.
In fact, 5 out of this 6 methods deal with thread synchronization
(notify()
, notifyAll()
and the
3 versions of wait()
). Those method should not be
used since an active object provides thread synchronization. Indeed,
using the standard thread synchronization mechanism and ProActive
thread synchronization mechanism at the same time might conflict and
result in an absolute debugger's nightmare.
The last final method in the class Object
is getClass()
. When invoked on an active object,
getClass()
is not reified and therefore performed
on the stub object, which returns an object of class
Class
that represents the class of the stub
(Stub_B
in our example) and not the class of the
active object itself (B
in our example). However,
this method is seldom used in standard applications and it doesn't
prevent the operator instanceof
to work thanks to
its polymorphic behavior. Therefore the expression (foo
instanceof B)
has the same value whether B is active or
not.
Getting or setting instance variables directly (not through a
getter or a setter) must be avoided in the case of active objects
because it results in getting or setting the value on the stub
object and not on the instance of the class B
.
This problem is usually worked around by using get/set methods for
setting or reading attributes. This rule of strict encapsulation may
also be found in JavaBeans or in most distributed object systems
like RMI or CORBA.
The role of the proxy is to handle asynchronism in calls to active
object. More specifically, it creates future objects if possible and
needed, forwards calls to bodies and returns future objects to the
stubs. As this class operates on MethodCall
objects,
it is absolutely generic and does not depend at all on the type of the
stub that feeds calls in through its reify
method.
The body
is responsible for storing calls
(actually, Request
objects) in a queue of pending
requests and processing these request according to a given
synchronization policy, whose default behavior is FIFO. The Body has its
own thread, which alternatively chooses a request in the queue of
pending ones and executes the associated call.
This is a standard instance of class B
. It may
contain some synchronized information in its live
method, if any. As the body executes calls one by one, there cannot be
any concurrent execution of two portions of code of this object by two
different threads. This enables the use of pre- and post-conditions and
class invariants. As a consequence, the use of the keyword
synchronized
in class B
should not
be necessary. Any synchronization scheme that can be expressed through
monitors and synchronized
statements can be expressed
using ProActive's high-level synchronization mechanism in a much more
natural and user-friendly way.
Whenever possible a method call on an active object is reified as an asynchronous request. If not possible the call is synchronous and blocks until the reply is received. In case the request is asynchronous, it immediately returns a future object.
This object acts as a placeholder for the result of the not-yet-performed method invocation. As a consequence, the calling thread can go on with executing its code, as long as it doesn't need to invoke methods on the returned object, in which case the calling thread is automatically blocked if the result of the method invocation is not yet available. Below are shown the different cases that can lead to an asynchronous call.
Return type | Can throw checked exception | Creation of a future | Asynchronous |
void | - | No | Yes |
Non Reifiable Object | - | No | No |
Reifiable Object | Yes | No | No |
Reifiable Object | No | Yes | Yes |
Table 13.1. Future creation, and asynchronous calls depending on return type
As we can see, the creation of a future depends not only on the caller type, but also on the return object type. Creating a future is only possible if the object is reifiable. Note although having a quite similar structure as an active object, a future object is not active. It only has a Stub and a Proxy as shown in figure below:
During its lifetime, an active object can create many future objects. There are all automatically kept in a FuturePool.
Each time a future is created, it is inserted in the future pool
of the corresponding active object. When the result becomes available,
the future object is removed from the pool. Although most of the methods
of the FuturePool are for internal use only and are directly called by
the proactive library we provide a method to wait until a result becomes
available. Instead of blocking until a specific future is available, the
call to waitForReply()
blocks until any of the
current futures become available. An application can be found in the
FutureList class.
Any call to a future object is reified in order to be blocked if
the future is not yet available and later executed on the result
object. However, two methods don't follow this scheme: equals and
hashCode. They are often called by other methods from the Java
library, like HashTable.add()
and so are most of
the time out of control from the user. This can lead very easily to
deadlocks if they are called on a not yet available object.
Instead of returning the hashcode of the object, it returns the hashcode of its proxy. Since there is only one proxy per future object, there is a unique equivalence between them.
The default implementation of equals()
in the
Object class is to compare the references of two objects. In ProActive
it is redefined to compare the hashcode of two proxies. As a
consequence it is only possible to compare two future object, and not
a future object with a normal object.
There are some drawbacks with this technique, the main one being
the impossibility to have a user override the default
HashTable
and equals()
methods.
The toString()
method is most of the time
called with System.out.println()
to turn an object
into a printable string. In the current implementation, a call to this
method will block on a future object like any other call, thus, one
has to be careful when using it. As an example, trying to print a
future object for debugging purpose will most of the time lead to a
deadlock. Instead of displaying the corresponding string of a future
object, you might consider displaying its hashCode.
First, let's introduce the example we'll use throughout this
section. Let us say that some piece of code in an instance of class
A
calls method foo
on an active
instance of class B
. This call is asynchronous and
returns a future object of class V
. Then, possibly
after having executed some other code, the same thread that issued the
call calls method bar
on the future object returned
by the call to foo
.
In a sequential, single-threaded version of the same
application, the thread would have executed the code of the calling
method in class A
up to the call of
foo
, then the code of foo
in
class B
, then back to the code of the calling
method in class A
up to the call to
bar
, then the code of bar
in
class V
, and finally back to the code of the
calling method in class A
until its end. The
sequence diagram below summarizes this execution. You can notice how
the single thread successively executes code of different methods in
different classes.
Let us first get an idea of what the graph of objects at execution (the objects with their references to each other) looks like at three different moments of the execution:
Before calling foo
, we have exactly the
same setup as after the creation of the active instance of
B
and summarized in the figure below: an
instance of class A
and an active instance of
class B
. As all active objects, the instance of
class B
is composed of a stub (an instance of
class Stub_B
, which actually inherits directly
from B
), a BodyProxy
, a
Body
and the actual instance of
B
.
After the asynchronous call to foo
has
returned, A
now holds a reference onto a future
object representing the not-yet-available result of the call. It
is actually composed of a Stub_V
and a
FutureProxy
as shown on the figure
below.
Right after having executed foo
on the
instance of B
, the thread of the
Body
sets the result in the future, which
results in the FutureProxy
having a reference
onto a V
(see figure below).
Let us now concentrate on how and when and by which thread the
different methods are called. We have two threads: the thread that
belongs to the subsystem A
is part of (let's call
it the first thread), and the thread that belongs
to the subsystem B
is part of (the second
thread).
The first thread invokes foo
on an instance
of Stub_B
, which builds a
MethodCall
object and passes it to the
BodyProxy
as a parameter of the call to
reify
. The proxy then checks the return type of the
call (in this case V
) and generates a future object
of type V
for representing the result of the method
invocation. The future object is actually composed of a
Stub_V
and a FutureProxy
. A
reference onto this future object is set in the
MethodCall
object, which will prove useful once the
call is executed. Now that the MethodCall
object is
ready, it is passed as a Request to the Body
of the
Active Object as a parameter. The body simply appends this request to
the queue of pending requests and returns immediately. The call to
foo
that an A
issued now returns
a future object of type Stub_V
, that is a subclass
of V
.
At some point, possibly after having served some other requests,
the second thread (the active thread) picks up
the request issued by the first thread some time
ago. It then executes the embedded call by calling
foo
on the instance of B
with
the actual parameters stored in the MethodCall
object. As specified in its signature, this call returns an object of
type V
. The second thread is
then responsible for setting this object in the future object (which
is the reason why MethodCall
objects hold a
reference on the future object created by the
FutureProxy
). The execution of the call is now
over, and the second thread can select another
request to serve in the queue and execute it.
In the meantime, the first thread has
continued executing the code of the calling method in class
A
. At some point, it calls bar
on the object of type Stub_V
that was returned by
the call to foo
. This call is reified thanks to the
Stub_V
and processed by the
FutureProxy
. If the object the future represents is
available (the second thread has already set it
in the future object, which is described in figure below, the call is
executed on it and returns a value to the calling code in
A
.
If it is not yet available, the first thread is suspended in
FutureProxy
until the second thread sets the result
in the future object (see figure below).
There are few things to remember with asynchronous method calls and futures, in order to avoid annoying debugging sessions:
Constructor with no-args: this constructor will be used either for the Active Objects creation(if not present, an exception might be thrown) or Future creation for a method call (if not present, the method call is synchronous). Avoid to put initialization stuff in this constructor, as it might lead to unexpected behavior. Indeed this constructor is called for the stub creation.
Make your classes implement Serializable interface since ProActive deals with objects that cross the network
Think to use wrappers instead of primitive types or final classes for methods result type otherwise you will loose the asynchronism capabilities. For instance if one of your object has a method
int giveSolution(parameter)
calling this method with ProActive is sychronous. So to keep the asynchronism it is advised to use
IntWrapper giveSolution(parameter)
In that case call to this method is asynchronous.
All wrappers are in the package: org.objectweb.proactive.core.util.wrapper
ProActive provides more used primitive type wrappers, there are 2 versions of each, one mutable, and the other which is immutable.
Only the methods return type are concerned not the parameters.
Avoid to return null in Active Object methods: on the caller side the test if(result_from_method == null) has no sense. Indeed result_from_method is a couple Stub-FutureProxy as explained above, so even if the method returns null, result_from_method cannot be null:
public class MyObject{ public MyObject(){ //empty constructor with no-args } public Object getObject{ if(.....) { return new Object(); } else { return null; --> to avoid in ProActive } } }
On the caller side:
MyObject o = new MyObject(); Object result_from_method = o.getObject(); if(result_from_method == null){ ...... }
This test is never true, indeed, result_from_method is Stub-->Proxy-->null if the future is not yet available or the method returns null or Stub-->Proxy-->Object if the future is available, but result_from_method is never null.
An Automatic Continuation is due to the propagation of a future outside the activity that has sent the corresponding request.
Automatic Continuations allow to pass in parameter or return as a result future objects(or objects containing a future) without blocking to wait the result object of the future. When the result is available on the object that originated the creation of the future, this object must update the result in all objects to which it passed the future.
Message sending
Automatic Continuations can occur when sending a request (parameter of the request is a future or contains a future) or when sending a reply (the result is a future or contains a future).
Outgoing futures are registered in the FuturePool of the Active Object sending this future(request or reply). Registration for couple(Future,BodyDestination) as an Automatic Continuation occurs when the future is serialized(indeed every request or reply are serialized before being sent, and the future is part of the request or the reply). More precisely, a thread T sending the message(request or reply)---therefore the thread doing the serialization---, keeps in a static table (FuturePool.bodyDestination) a reference of the destination body. Hence when a future F is serialized by the same thread T(since futures are part of request or reply, it is the same thread serializing the request --or reply-- and the future), it looks up in the static table, if there is a destination D registered for the thread T. If true, the future notifies its FuturePool (that it is going to leave), which in turn registers couple (F,D) as an Automatic Continuation
When value V is available for the future F, V is propagated to all objects that received the fututre F. This Update is realized by a particular thread located in the FuturePool.
Message reception
When a message is received(request or reply) by an Active Object, this message can contain a future. So the Active Object registers this future in the FuturePool to be able to update it when the value will be available. This registration takes place in two steps:
When the future is deserialized, it registers in a static table (FuturePool.incomingFutures
In Receive[Request-Reply] method, it is checked if one or many futures are registerd in that table, then, if true these futures are registerd in the FuturePool in a standart way.
The following piece of code shows both cases: passing a future as parameter or as a result.
class C { .... public static void main(String[] args){ ...... A a = newActive(A); A b = newActive(B); Result r1 = a.foo(); //r1 is a future Result r2 = b.bar(r1); //r1 is passed as parameter Result r3 = b.bar2(); // see ** ........ } //end of main ... } //end of class C
where
class A { ... public Result foo(){ ... } ... } //end of class A
and
class B { ... public Result bar (Result r) { ... } public Result bar2 () { A a = newActive(A); return a.foo(); // ** future is sent as a result } } //end of class B
We will illustrate here how a future is first created, then passed as parameter to a method later on.
Let us say that some piece of code in main method of an object C calls method foo() on an instance of class A.
This call is asynhronous and returns a future object Future_r1 of class Result.
Then method bar() is called on an instance of class B passing future Future_r1 as a parameter to the method
This call is asynhronous and returns a future object Future_r2 of class Result. B needs the value of Future_r1 which is not yet available in order to return the result of method bar(), so it gets the future too.
The value of the result for the call to method foo is now available, so A updates the value of Future_r1
C updates the value of Future_r1 for B
B returns the value for the call to method bar() and updates the value of Future_r2 for C
This example implements a very simple client-server application. A
client object display a String
received from a remote
server. We will see how to write classes from which active and remote
objects can be created, how to find a remote object and how to invoke
methods on remote objects.
Only two classes are needed: one for the server object
Hello
and one for the client that accesses it
HelloClient
.
This class implements server-side functionalities. Its creation involves the following steps:
Provide an implementation for the required server-side functionalities
Provide an empty, no-arg constructor
Write a main
method in order to
instantiate one server object and register it with an URL.
public class Hello { private String name; private String hi = "Hello world"; private java.text.DateFormat dateFormat = new java.text.SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); public Hello() { } public Hello(String name) { this.name = name; } public String sayHello() { return hi + " at " + dateFormat.format(new java.util.Date())+ " from node: " + org.objectweb.proactive.ProActive.getBodyOnThis().getNodeURL(); } public static void main(String[] args) { // Registers it with an URL try { // Creates an active instance of class HelloServer on the local node Hello hello = (Hello)org.objectweb.proactive.ProActive.newActive(Hello.class.getName(), new Object[]{"remote"}); java.net.InetAddress localhost = java.net.InetAddress.getLocalHost(); org.objectweb.proactive.ProActive.register(hello, "//" + localhost.getHostName() + "/Hello"); } catch (Exception e) { System.err.println("Error: " + e.getMessage()); e.printStackTrace(); } } }
Example 13.4. A possible implementation for the Hello
class:
Implementing any remotely-accessible functionality is simply done through normal Java methods in a normal Java class, in exactly the same manner it would have been done in a non-distributed version of the same class. This has to be contrasted with the RMI approach, where several more steps are needed:
Define a remote interface for declaring the remotely-accessible methods.
Rewrite the class so that it inherits from
java.rmi.server.UnicastRemoteObject
, which is
the root class of all remote objects.
Add remote exceptions handling to the code.
You may have noticed that class Hello
has a
constructor with no parameters and an empty implementation. The
presence of this empty no-arg constructor is imposed by ProActive
and is actually a side-effect of ProActive's transparent
implementation of active remote objects (as a matter of fact, this
side-effect is caused by ProActive being implemented on top of a
100% Java metaobject protocol). If no such constructor is provided,
active objects cannot be created.
If no constructor at all is provided, active objects can still be created because, in this specific case, all Java compilers provide a default no-arg empty constructor. If a no-arg constructor is provided but its implementation is not empty, unwanted behavior may appear because the no-arg constructor is always called when an active object is created, whatever code the user can write.
Now that we know how to write the class that implements the
required server-side functionalities, let us see how to create the
server object. In ProActive, there is actually no difference between
a server and a client object as both are remote objects.Creating the
active object is done through instantiation-based
creation. We want this active object to be created on the
current node, which is why we use newActive
with
only two parameters. In order for the client to obtain an initial
reference onto this remote object, we need to register it in the
registry (which is actually the well-known
rmiregistry
) with a valid RMI URL.
The responsibility of this class is first to locate the remote server object, then to invoke a method on it in order to retrieve a message, and finally display that message.
public class HelloClient { public static void main(String[] args) { Hello myServer; String message; try { // checks for the server's URL if (args.length == 0) { // There is no url to the server, so create an active server within this VM myServer = (Hello)org.objectweb.proactive.ProActive.newActive(Hello.class.getName(), new Object[]{"local"}); } else { // Lookups the server object System.out.println("Using server located on " + args[0]); myServer = (Hello)org.objectweb.proactive.ProActive.lookupActive(Hello.class.getName(), args[0]); } // Invokes a remote method on this object to get the message message = myServer.sayHello(); // Prints out the message System.out.println("The message is: " + message); } catch (Exception e) { System.err.println("Could not reach/create server object"); e.printStackTrace(); System.exit(1); } } }
Example 13.5. HelloClient.java
The operation of lookup simply means
obtaining a reference onto an object from the URL it is bound to.
The return type of method
Proactive.lookupActive()
is
Object
, then we need to cast it down into the
type of the variable that holds the reference
(Hello
here). If no object is found at this URL,
the call to Proactive.lookupActive()
returns
null
.
This is exactly like invoking a method on a local object of
the same type. The user does not have to deal with catching
distribution related exceptions like, for example, when using RMI or
CORBA. Future versions of ProActive will provide an exception
handler mechanism in order to process these exceptions in a separate
place than the functional code. As class String
is final
, there cannot be any asynchronism here
since the object returned from the call cannot be replaced by a
future object (this restriction on final
classes
is imposed by ProActive's implementation).
In order to run both the client and server in the same VM, the client creates an active object in the same VM if it doesn't find the server's URL. The code snippet which instantiates the Server in the same VM is the following:
if (args.length == 0) { // There is no url to the server, so create an active server within this VM myServer = (Hello)org.objectweb.proactive.ProActive.newActive( Hello.class.getName(), new Object[]{"local"}); }
To launch the Client and the Server, just type:
linux> java -Djava.security.policy=scripts/proactive.java.policy -Dlog4j.configuration=file:scripts/proactive-log4j org.objectweb.proactive.examples.hello.HelloClient
windows> java -Djava.security.policy=scripts\unix\proactive.java.policy -Dlog4j.configuration=file:scripts\unix\proactive-log4j org.objectweb.proactive.examples.hello.HelloClient &
Just start the main
method in the
Hello
class.
linux> java -Djava.security.policy=scripts/proactive.java.policy -Dlog4j.configuration=file:scripts/proactive-log4j org.objectweb.proactive.examples.hello.Hello &
windows> java -Djava.security.policy=scripts\proactive.java.policy -Dlog4j.configuration=file:scripts\proactive-log4j org.objectweb.proactive.examples.hello.Hello
linux> java -Djava.security.policy=scripts/proactive.java.policy -Dlog4j.configuration=file:scripts/proactive-log4j org.objectweb.proactive.examples.hello.HelloClient //localhost/Hello &
windows> java -Djava.security.policy=scripts\proactive.java.policy -Dlog4j.configuration=file:scripts\proactive-log4j org.objectweb.proactive.examples.hello.HelloClient //localhost/Hello
Log on to the server's host, and launch the
Hello
class.
linux remoteHost> java -Djava.security.policy=scripts/proactive.java.policy -Dlog4j.configuration=file:scripts/proactive-log4j org.objectweb.proactive.examples.hello.Hello &
windows remoteHost> java -Djava.security.policy=scripts\proactive.java.policy -Dlog4j.configuration=file:scripts\proactive-log4j org.objectweb.proactive.examples.hello.Hello
Log on to the client Host, and launch the client
linux clientHost> java -cp $CLASSPATH -Djava.security.policy=scripts/proactive.java.policy -Dlog4j.configuration=file:scripts/proactive-log4j org.objectweb.proactive.examples.hello.HelloClient //remoteHost/Hello &
windows clientHost> java -cp $CLASSPATH -Djava.security.policy=scripts\proactive.java.policy -Dlog4j.configuration=file:scripts\proactive-log4j org.objectweb.proactive.examples.hello.HelloClient //remoteHost/Hello
Note | |
---|---|
There is also a Guided Tour section on the Hello world example: Chapter 6, Hands-on programming |
© 2001-2007 INRIA Sophia Antipolis All Rights Reserved