Remote Consistency Framework

ReflexD makes use of a _Remote Consistency Framework_ (RFC) developed on top of Reflex too. In this page we will see the implementation details of this framework.

Fundamentals

When we use RMI to make a remote invocation, we are using the serialization mechanism too. A consequence of the use of this mechanism is that the serialized objects are completely independent from the original ones. The standard way (making use of RMI) to keep the relation between these objects is to make the original one remote, but this implies the implicit programming of the distribution concern, that is what we are trying to avoid.

The RCF objective is to keep distributed copies of a given object consistent. In order to do this, a enhanced (but compatible) copy of the object is transfered. This enhanced copy is able to communicate with its remote counterpart in order to synchronize both of them.

Definitions

First of all, we need to define two kinds of objects in order to avoid further misunderstandings:

  • Local objects: objects that were designed to work locally on the host that created them and do not know about the distributed concern of the application they belong to.
  • Remote objects: objects that know they are running on a host (which is not necessarily the one that created them) that has a remote counterpart they have to communicate to.

Making use of these two concepts, we can rewrite the framework working as the ability to transform local objects to remote objects.

Consistent Objects

A consistent object is an instance of a class that declares itself as needed for consistency (lets call this class a consistent class). The main idea is to keep this declaration as simple as possible without limiting its powerfulness.

Consistent class declaration

A consistent class declaration involves: (a) the implementation of an interface and (b) the annotation of the class. These actions provides the necessary information in order to transform the class.

Lets see an example of a local object class definition:

public class Properties{
 
    private Map properties = new HashMap();
 
    public void setProperty(String key, String value){
        properties.put(key, value);
    }
 
    public String getProperty(String key){
        return (String) properties.get(key);
    }
}

And now, the corresponding consistent definition (with the interface and the annotations):

public class Properties implements Consistent{
 
    private Map properties = new HashMap();
 
    @Mutator
    public void setProperty(String key, String value){
        properties.put(key, value);
    }
 
    public String getProperty(String key){
        return (String) properties.get(key);
    }
 
    public IConsistencyPolicy getPolicy(){
        return ...;
    }
}

As you can see, the differences are:

  • The interface being implemented (and its corresponding method getpolicy).
  • The annotation on the setproperty method specifying that it mutates the state of the object.

With these two elements, the framework is able to create a new class that will be used instead of the original one. To achieve this, the framework intercepts all the original class constructor calls and:

  1. Creates a subclass of the object's class.
  2. Defines a metaobject/link/hookset for that class. This second metaobject is in charge of handling the distribution aspect.
  3. The class instrumented by Reflex according to the rules defined by the previous elements.
  4. Creates a new instance of this class invoking the same constructor that was originally invoked (now inherited in the subclass)
  5. And finally, (exploiting the ability of Reflex to replace the instance that, in absence of the framework, will be created), returns this dynamically generated instance.

The generated subclass looks like this:

public class Properties1 extends Properties{
 
    private Map properties = new HashMap();
    private ConsistencyMO consistencyMO = new ConsistencyMO();
 
    public void setProperty(String key, String value){
        if(synchMOActive()){
            consistencyMO.replace("setProperty", [key, value]);
            return;
        }
 
        properties.put(key, value);
    }
}

} The ConsistencyMO instance we use here is in charge of keeping consistent the distributed copies of the object according to the consistency policy the method getPolicy returns.

Now we will see in detail the consistency policies.

Synchronization Policies

A consistency policy is charge of determining if a certain method call should be called in a remote host. The definition of the IConsistencyPolicy interface, which is what you have to implement in order to create a new kind of policy, is:

public interface IConsistencyPolicy extends Serializable{
 
    public Object doLocalDelegation(ConsistencyMO syncMO, String methodName, Object[] args);
 
    public Object doRemoteDelegation(ConsistencyMO syncMO, String methodName, Object[] args);
 
    public IConsistencyRestriction getConsistencyRestriction();
}

The first two methods are invoked by the ConsistencyMO whenever a method call occur: * doLocalDelegation if the method call was in the host that created the synchronizable object or * doRemoteDelegation if the method call was in another host

The third method returns a consistency restriction (an instance of IConsistencyRestriction), that is in charge of telling the ConsistencyMO which methods are subject for synchronization. The IConsistencyRestriction interface is defined as:

public interface IConsistencyRestriction extends Serializable{
 
    public boolean evaluate(IConsistencyContext aContext, Object target, Method method);
}

Where its single method is called to know if the method method invoked on target is subject for synchronization. The additional IConsistencyContext parameter is provided as a point of entrance to context properties as: (a) the host address where the object was created and (b) a boolean that tells us if the call was in the host that created the object or not.

Generally, this policy will be based on the @Mutator annotation, but it can decide based on other elements. By example, a RemoteObjectPolicy which keeps an object consistency as it was a remote object is implemented this way:

public class RemoteObjectPolicy implements IConsistencyPolicy{
 
    public Object doLocalDelegation(ConsistencyMO syncMO, String methodName, Object[] args){
        //The method call occurred in the original host...
        //...Invoke the local method
    }
 
    public Object doRemoteDelegation(ConsistencyMO syncMO, String methodName, Object[] args){
        //The method call occurred in a remote host...
        //...Invoke the remote method
    }
 
    public IConsistencyRestriction getPolicyRestriction(){
        return new IConsistencyRestriction (){
            public boolean evaluate(IConsistencyContext aContext, Object target, Method method){
                //all methods are subject for consistency
                return true;
            };
        }
    }
}

Architecture

The principal components of the RCF are: (1) the consistency metaobject and (2) the consistency manager.

The consistency metaobject is the one in charge of intercepting the method calls in a consistent object. Once it has taken the control of the execution, it connects to a consistency manager and through RMI invokes a method to notify the method call occurrence.

The role of the consistency manager is to be a remote access point to the local host and therefore, a net bridge for notifications of method calls. Notice that this bridge is bi-directional as each host has its own consistency manager.

From a functional point of view, the previous process can be achieved making use solely of RMI. This is because we have developed the framework on top of RMI. Nevertheless, our contribution is the ease of use: you do not have to do anything related to RMI like starting a RMI registry or bind objects to it, declare remote objects, handle RMI-related exceptions, etc.