|
![]() |
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 in an Active Object, there are some restrictions that we will detail 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. This is 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
you must make sure that the arguments of the constructor 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. Remind also that to be created properly, the reified object must have a declared empty no-args constructor.
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 :
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.
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 be Serializable
. 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 :
Three interfaces are used to define and implement each step :
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.
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
) :
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 initializationRunActive
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 activityEndActive
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
This is the easiest solution when you do 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.
Example1:
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()); } }
Example2: start, stop, suspend, restart a simulation algorithm in runActivity method
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(); } }
This 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
. Here 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 :
Some other happen at the level of a method in a specific class:
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 able to create factories for each of those MetaObjects. This interface is implemented by ProActiveMetaObjectFactory 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 provides extra newActive and turnActive methods for that.
First you have to write a new MetaObject factory that inherits from ProActiveMetaObjectFactory or directly implements MetaObjectFactory 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 to simplify the creation of factories as singleton. More explanations can be found in the javadoc of that class.
The use of that new 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 = {}; 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 :
Stub_B
)BodyProxy
)Body
)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 here 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 :
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.
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.
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.
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.