Active Objects have the ability to migrate between nodes while the application is running. Whatever application was using the active objects will continue running regardless of the migration of active objects.
The migration of an active object can be triggered by the active
object itself, or by an external agent. In both cases a single method
will eventually get called to perform the migration. It is the method
migrateTo(...)
accessible from a migratable body (a body
that inherits from MigratableBody
).
In order to ease the use of the migration, ProActive provides 2 sets of
static methods in the PAMobileAgent
class. The first set is
aimed at the migration triggered from the active object that wants to
migrate. The methods rely on the fact that the calling thread
is the active thread of the active object:
PAMobileAgent.migrateTo(Object o)
: migrate to the same
location as an existing active object
PAMobileAgent.migrateTo(String nodeURL)
: migrate to the
location given by the URL of the node
PAMobileAgent.migrateTo(Node node)
: migrate to the location
of the given node
The second set is aimed at the migration triggered from another
agent than the target active object. In this case the external agent must
have a reference to the Body of the active object it wants to
migrate. The boolean
value in the following methods
indicates if the migration modifies the application's computation.
PAMobileAgent.migrateTo(Body body, Object o, boolean
isNFRequest)
: migrate to the same location as an existing
active object
PAMobileAgent.migrateTo(Body body, String nodeURL, boolean
isNFRequest)
: migrate to the location given by the URL of the
node
PAMobileAgent.migrateTo(Body body, Node node, boolean
isNFRequest)
: migrate to the location of the given node
PAMobileAgent.migrateTo(Body body, Node node, boolean isNFRequest,
int priority)
: migrate to the location of the given node
The int
value indicates the priority of the non functional request.
The levels are defined in the Request
interface.
Any active object has the ability to migrate. If it references some
passive objects, they will also migrate to the new location. Since we rely
on the serialization to send the object on the network, the active object must implement the serializable
interface. To migrate, an active object must have a method
which contains a call to PAMobileAgent.migrateTo(...)
. This call must be the
last one in the method, i.e the method must return immediately after. Here
is an example of a method in an active object:
public void moveTo(String t) { try { PAMobileAgent.migrateTo(t); } catch (Exception e) { e.printStackTrace(); } }
We don't provide any test to check if the call to migrateTo is the
last one in the method, hence if this rule is not enforced, it can lead to
unexpected behavior. To make this object migrate, you just have to call
its moveTo(String)
method.
import org.objectweb.proactive.ProActive; public class SimpleAgent implements Serializable { public SimpleAgent() {} //empty, no-arg constructor required by ProActive public void moveTo(String t) { try { PAMobileAgent.migrateTo(t); } catch (Exception e) { e.printStackTrace(); } } public String whereAreYou() { try { return InetAddress.getLocalHost().getHostName(); } catch (Exception e) { return 'Localhost lookup failed'; } } public static void main (String[] args) { if (!(args.length<0)) { System.out.println('Usage: java migration.test.TestSimple hostname/NodeName '); System.exit(-1); } SimpleAgent t = null; try { // create the SimpleAgent in this JVM t = (SimpleAgent) ProActive.newActive('migration.test.SimpleAgent',null); } catch (Exception e) { e.printStackTrace(); } // migrate the SimpleAgent to the location identified by the given node URL // we assume here that the node does already exist t.moveTo(args[0]); System.out.println('The Active Object is now on host ' + t.whereAreYou()); } }
The class SimpleAgent implements Serializable
so
the objects created will be able to migrate. We need to provide an empty
constructor to avoid side effects during the creation of active objects.
This object has two methods, moveTo()
which makes it
migrate to the specified location, and whereAreYou()
which returns the hostname of the new location of the agent.
In the main method, we first need to create an active object, which
is done through the call to newActive()
. Once this is
done, we can call methods on it as on any object. We call its
moveTo
method which will make it migrate to the node
specified as parameter and then we ask it what is its current
location. For our application we pass the location of the node as runtime
parameter to the application.
package active; import java.io.IOException; public class Main{ public static void main(String args[]) { try{ SimpleAgent ao = (SimpleAget)PAActiveObject.newActive( MigratableHello.class.getName(), new Object [] {}, null); //the first parameter past to the program is the node location ao.moveTo(args[0]); } catch (ActiveObjectCreationException aoExcep){ System.err.println(aoExcep.getMessage()); } } }
To see another example of migration look at the migration example in Chapter 6, Active Object Tutorial.
Implementing a MigrationStrategy (specifying what to do on departure and on arrival).
The migration of an active object uses serialization.
Unfortunately, not all the objects in the Java language are serializable so
the first step to solve the problem is to identify the unserializable
components.
In front the declaration of unserializable fields we will put the keyword transient
. This
indicates that the value of this variable should not be serialized.
After the first migration, these fields will be set to null since they have not been
saved. So we have to explicitly rebuild them upon arrival of the active object at its
new location. This can be done by using
the MigrationStrategyManager
interface or
by implementing readObject(java.io.ObjectInputStream in)
and
writeObject(java.io.ObjectOutputStream out)
from the
interface Serializable
.
We can use the methods onArrival(String)
and onDeparture(String)
to specify which methods will
be run when the active objects migrates.
The arguments for onArrival(String)
and onDeparture(String)
are the methods to be run before
serialization and after deserialization. We create a new
MigrationStrategyManagerImpl
for the body of the object to
be migrated and assign the methods to be run. For this we use the
initActivity(Body)
method because we only need to run
the creation and assignment once.
public class SomeWhatUnserializable implements MigrationStrategyManager { private SerializableType saved; //will store the values private transient UnserializableType toBeSaved; //will be null after serialization public void initActivity(Body body) { ..... MigrationStrategyManagerImpl myStrategyManager = new MigrationStrategyManagerImpl( org.objectweb.proactive.core.body.migration.Migratable) body); myStrategyManager.onArrival("rebuild"); myStrategyManager.onDeparture("leaveHost"); } public void rebuild(){ toBeSaved = saved.getValues(); //other rebuild actions } public void leaveHost(){ saved = toBeSaved.getValues(); //saves the value of the unserializable type //other save actions } }
Another way of dealing with unserializable attributes is using the methods from the Serializable interface:
private void writeObject(java.io.ObjectOutputStream out) throws IOException private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException; private void readObjectNoData() throws ObjectStreamException;
We override these methods to provide a way to save and restore the non-serializable components of the active object:
public class SomeWhatUnserializable implements MigrationStrategyManager { private transient UnserializableType toBeSaved; //will be null after serialization private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { //do the default serialization out.defaultWriteObject(); //do the custom serialization out.writeObject(toBeSaved.getValues()); } private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { //do the default deserialization in.defaultReadObject(); //do the custom deserialization toBeSaved = in.readObject(); } }
To learn how to use the methods see the Serializable interface in the standard JavaDoc.
There are two way to communicate with an active object which has migrated :
Forwarders
An active object upon leaving a site leaves behind a special object, a forwarder, which is in charge of forwarding incoming messages to the next destination. As time goes, a chain of forwarders builds between a caller and the mobile object. Any message sent to the latter will go through the chain to reach the agent. There is a virtual path between a caller and a mobile object. As soon as a call is peformed on the agent the chain of forwarders is shortened into a direct link to the agent called.
Location Server
Communicating with a mobile object can be done with an explicit reference towards the mobile entity, which requires the means to get its current location when necessary.
There is a two-step communication: first there should be a search to obtain an up-to-date reference (localization), and then the actual communication. The simplest solution is to have a unique location server which maintains a database of the known position of the agents. When an object wants to communicate with an object which has migrated, it queries the server which sends back a new reference. If this is a correct reference then the communication takes place, otherwise a new query is issued.
Both techniques have their drawbacks. Two problems arise when using a forwarding scheme, especially if the ambition is scalable mobile agents over WAN. First, the forwarders use resources on a site as long as they have not been garbage collected. Thus if a chain exists between to objects, it will remains even if there is no new communications going by. Second, the longer the chain is, the more likely it will be cut because of a hardware or software failure. As a consequence, while forwarders are more efficient under some conditions, they do not appear to be scalable, nor reliable.
The server on the other hand is a single point of failure and a potential bottleneck. If a server is to help communicating with a higher number of mobile agents, then it might not be able to serve requests quickly enough. Furthermore, in case of a crash, it is not possible to communicate with mobile active objects until the server is back. It is possible to avoid most of these issues by having redundant servers with load balancing at the cost of increasing complexity.
Based on these observations and taking into account the variability of the environment, we use a configurable communication protocol which offers the main benefits from both the forwarder and the server while avoiding their drawbacks. Configurable with discrete and continuous parameters, it can be tailored to the environment to offer both performance and reliability.
Forwarders and agents have several parameters which control their behaviour:
forwarder time to live in milliseconds -
proactive.mixedlocation.ttl
Time limited forwarders remain alive only for a limited period. When their lifetime is over, they can be removed. First of all, this brings an important advantage: scalability due to absence of the DGC and the systematic reclaim of forwarding resources. However this increases the risks of having the forwarding chain cut since a forwarder might expire during the normal execution of the application. In such a situation, we rely on a server which will be considered as an alternative solution. This increases the overall reliability.
Use -1 to indicate that the forwarders have a unlimited lifetime.
forwarder updating status, boolean
- proactive.mixedlocation.updatingForwarder
It is possible to rely on the forwarders to maintain the location
of the agent by having them update the server. When they reach the end
of their lifetime, they can send to the server their outgoing
reference which could be the address of the agent or another forwarder.
The updatingForwarder parameter can be true or false. If true
it releases the agent from most of the updates.
In order to increase reliability, it is possible to have also the agent
update the server on a regular basis. This value can be true
or false
.
agent time to update in milliseconds
Each mobile agent has a nominal Time To Update (TTU) after which it will inform the localization server of its new location. The TTu is defined as the first occurence of two potential events since the last update:
agent migrations number, integer -
proactive.mixedlocation.maxMigrationNb
This parameter ndicates the number of migrations before updating the server. Use -1 to indicate that the agent never updates the server.
agent time on site in milliseconds -
proactive.mixedlocation.maxTimeOnSite
The parameter indicates the maximum time spent on a site before updating the server. You can use -1 to indicate that agent will not inform the server.
If we consider that both the agent and the forwarders can send updates to the server, the server must be able to make the difference between messages from the forwarders and from the agent; those are always the most up to date. Also, since we don't have any constraint on the Time To Live (TTL) of the forwarders, it could be that a forwarder at the beginning of a chain dies after on at the end. If this happens and we are not careful when dealing with the requests, the server could erase a more up to date reference with an old one.
As a default, ProActive uses a "Forwarder based" strategy . It means that the forwarders have a unlimited lifetime and the agent never updates the location server.
To configure your own strategy, you have to
edit the file
ProActive/src/Core/org/objectweb/proactive/core/config/ProActiveConfiguration.xml
and change the four values listed above.
<?xml version="1.0" encoding="UTF-8"?> <ProActiveUserProperties> <properties> <prop key="proactive.locationserver" value="org.objectweb.proactive.ext.locationserver.LocationServer" /> <prop key="proactive.locationserver.rmi" value="//localhost/LocationServer" /> <prop key="proactive.securitymanager" value="true" /> <prop key="proactive.mixedlocation.ttl" value="-1" /> <prop key="proactive.mixedlocation.updatingForwarder" value="false" /> <prop key="proactive.mixedlocation.maxMigrationNb" value="-1" /> <prop key="proactive.mixedlocation.maxTimeOnSite" value="-1" /> <prop key="proactive.net.nolocal" value="false" /> <prop key="proactive.net.noprivate" value="false" /> <prop key="proactive.useIPaddress" value="true" /> <prop key="proactive.future.ac" value="true" /> <prop key="proactive.stack_trace" value="false" /> <!-- complete stack traces in requests --> <prop key="proactive.dgc" value="false" /> <prop key="proactive.exit_on_empty" value="false" /> <!-- destroy the JVM when there is no more active object --> <prop key="schema.validation" value="true" /> <prop key="proactive.communication.protocol" value="rmi" /> <prop key="proactive.rmi.port" value="1099" /> <!--<prop key="proactive.java.policy" value="../../../../../../scripts/unix/proactive.java.policy"/>--> <prop key="fractal.provider" value="org.objectweb.proactive.core.component.Fractive" /> <!-- <prop key="proactive.tunneling.connect_timeout" value="2000"/> --> <prop key="proactive.tunneling.try_normal_first" value="true" /> <prop key="proactive.tunneling.use_gc" value="true" /> <prop key="proactive.tunneling.gc_period" value="10000" /> <prop key="proactive.classloader" value="false" /> <!-- <prop key="proactive.ssh.known_hosts" value="/user/mlacage/home/.ssh/known_hosts"/> --> <!-- <prop key="proactive.ssh.key_directory" value="/user/mlacage/home/.ssh/"/> --> <!-- <prop key="proactive.ssh.port" value="22"/> --> <!-- <prop key="proactive.ssh.username" value="mlacage"/> --> <prop key="components.creation.timeout" value="10000" /> <!-- timeout in seconds for parallel creation of components --> <!-- ***************************************************** --> <!-- P2P PROPERTIES VALUE --> <!-- THESE ARE DEFAULT VALUES --> <!-- Acquisition method --> <prop key="proactive.p2p.acq" value="rmi" /> <!-- Port number --> <prop key="proactive.p2p.port" value="6444" /> <!-- NOA is in number of peers --> <prop key="proactive.p2p.noa" value="3" /> <!-- TTU is in ms --> <prop key="proactive.p2p.ttu" value="60000" /> <!-- TTL is in hops --> <prop key="proactive.p2p.ttl" value="5" /> <!-- List capacity of message sequence number --> <prop key="proactive.p2p.msg_capacity" value="1000" /> <!-- Percentage of agree responses --> <prop key="proactive.p2p.expl_msg" value="66" /> <!-- Timeout for node acquisition --> <prop key="proactive.p2p.nodes_acq_to" value="180000" /> <!-- Lookup frequency for nodes --> <prop key="proactive.p2p.lookup_freq" value="30000" /> <!-- If true deploying one shared nodes by CPU, else only one node is shared --> <prop key="proactive.p2p.multi_proc_nodes" value="false" /> <!-- ***************************************************** --> <!-- ***************************************************** --> <!-- MASTER/WORKER PROPERTIES VALUE --> <!-- Period of the slave pinging (in ms) --> <prop key="proactive.masterworker.pingperiod" value="10000" /> <!-- Compressing tasks in the repository --> <prop key="proactive.masterworker.compresstasks" value="false" /> <!-- Workaround to fix rmissh/unicore conflict --> <prop key="proactive.unicore.forkclient" value="true" /> <prop key="proactive.runtime.stayalive" value="true" /> <!-- File Transfer Properties --> <prop key="proactive.filetransfer.services_number" value="16" /> <prop key="proactive.filetransfer.blocks_number" value="8" /> <prop key="proactive.filetransfer.blocks_size_kb" value="512" /> <prop key="proactive.filetransfer.buffer_size_kb" value="256" /> </properties> <javaProperties> <prop key="java.protocol.handler.pkgs" value="org.objectweb.proactive.core.ssh" /> <prop key="ibis.name_server.host" value="localhost" /> <prop key="ibis.name_server.key" value="1" /> <prop key="ibis.io.serialization.classloader" value= "org.objectweb.proactive.core.mop.MOPClassLoader" /> <prop key="ibis.serialization" value="ibis" /> </javaProperties> </ProActiveUserProperties>
A location server is available in the package org.objectweb.proactive.core.body.migration.MixedLocationServer. It can be launched using scripts/unix/migration/LocationServer. You can indicate on which node it have to be running.
Limitation : there can be only one LocationServer for the migration.
© 1997-2008 INRIA Sophia Antipolis All Rights Reserved