Activation

Contents

1. Introduction

Many of the examples in earlier chapters use RMI proxies for services. These services subclass UnicastRemoteObject, and live within a server whose principal task is to keep the service alive and registered with lookup services. If the server fails to renew leases then lookup services will eventually discard it; if it fails to keep itself and its service alive then the service will not be available when a client wants to use it.

This results in a server and a service which most of the time will be idle, probably swapped out to disk but still using virtual memory. In JDK 1.2, the memory requirements can be enormous (hopefully this will get fixed, but at the moment this is a severe embarrassment to Java, and a potential threat to the success of Jini). In JDK 1.2, there is an extension to RMI called activation which allows an idle object to die, and be recalled to life when needed. In this way, it does not occupy virtual memory while idle. Of course, a process needs to be alive to restore such objects, and RMI supplies a daemon rmid to manage this. In effect, rmid acts as another virtual memory manager as it stores information about dormant Java objects in its own files and restores them from there as needed.

There is a serious limitation to rmid: it is a Java program itself and when running also uses enormous amounts of memory! So it only makes sense to use this technique when you expect to be running a number of largely idle services on the same machine. When a service is recalled to life, or activated, a new JVM may be started to run the object. This again increases memory use.

If memory use was the only concern, then there are a variety of other systems such as echidna which run multiple applications within a single JVM. These may be adequate to solve memory issues. However, RMI Activation is also designed to work with distributed objects, and allows JVM's to hold remote references to objects which are no longer active. Instead of throwing a remote exception on trying to access these objects, the Activation system tries to resurrect the object using rmid to give a valid (and new) reference. Of course, if it fails to do this, it will throw an exception anyway.

The principal place that this is used in the standard Jini distribution is with the reggie lookup service. reggie is an activatable service that starts, registers itself with rmid and then exits. Whenever lookup services are required, rmid restarts reggie in a new JVM. Clients of the lookup service are unaware of this mechanism: they simply make calls on their proxy ServiceRegistration object and the Activation system looks after the rest. The main problem is for the system administrator: getting reggie to work in the first place!

2. A Service using Activation

The major concepts in Activation are the activatable object itself (which extends java.rmi.activation.Activatable) and the environment in which it runs, an ActivationGroup. A JVM may have an activation group associated with it. If an object needs to be activated and there is already a JVM running its group then it is restarted within that JVM. Otherwise, a new JVM is started. An activation group may hold a bunch of co-operating objects (I think - check this!!!).

2.1 Service

Making an object into an activable object requires subclassing from Activatable, and using a special two-argument constructor which will be called when the object needs to be reconstructed. There is a standard implementation of this constructor which just passes up to the super class:


public ActivatableImpl(ActivationID id, MarshalledObject data)
    throws RemoteException {
    super(id, 0);
}
(The marshalled data is used for what???). This is the only change normally needed for a service. For example, the file classifier becomes


package activation;

import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationID;
import java.rmi.MarshalledObject;

import common.MIMEType;
import common.FileClassifier;
import option3.RemoteFileClassifier;

/**
 * FileClassifierImpl.java
 *
 *
 * Created: Wed Mar 17 14:22:13 1999
 *
 * @author Jan Newmarch
 * @version 1.0
 */

public class FileClassifierImpl extends Activatable
                                implements RemoteFileClassifier {
    
    public MIMEType getMIMEType(String fileName) 
	throws java.rmi.RemoteException {
	System.out.println("Called with " + fileName);
	try {Runtime.getRuntime().exec("/usr/bin/whoami");} catch(Exception e){e.printStackTrace();}
	java.util.Properties props = System.getProperties();
	System.out.println("user " + props.getProperty("user.name"));
	if (new java.io.File("/etc/at.deny").canRead())
	    System.out.println("can read at.deny");
	else
	    System.out.println("can't read at.deny");
        if (fileName.endsWith(".gif")) {
            return new MIMEType("image", "gif");
        } else if (fileName.endsWith(".jpeg")) {
            return new MIMEType("image", "jpeg");
        } else if (fileName.endsWith(".mpg")) {
            return new MIMEType("video", "mpeg");
        } else if (fileName.endsWith(".txt")) {
            return new MIMEType("text", "plain");
        } else if (fileName.endsWith(".html")) {
            return new MIMEType("text", "html");
        } else
            // fill in lots of other types,
            // but eventually give up and
            return new MIMEType(null, null);
    }


    public FileClassifierImpl(ActivationID id, MarshalledObject data)  
	throws java.rmi.RemoteException {
	super(id, 0);
    }
    
} // FileClassifierImpl

2.2 Server

The server needs to create an activation group for the objects to run in. The main issue in this is to set a security policy file. There are two security policies in activatable objects: the policy used to create the service and export it, and the policy used to restore it. The activation group uses its policy file for restoration. The policy file for initial creation is set using the normal -Djava.security.policy=... argument to starting the server. After setting various parameters, the activation group is set for the JVM by ActivationGroup.createGroup().

The ``standard'' remote objects that subclass UnicastRemoteObject are created in the normal way using a constructor. Activatable objects are not constructed in the server but are instead registered with rmid, which will look after construction on an as-needed basis. rmid needs to know the class name and the location of the class files. These are wrapped up in an ActivationDesc, and registered with rmid by Activatable.register(). This returns a stub object that can be registered with lookup services using the ServiceRegistrar.register() methods.

The changes for servers from the unicast remote objects are

  1. An activation group has to be created with a security policy file
  2. The service is not created explicitly but instead registered with rmid
  3. The return object from this registration is a stub that can be registered with lookup services
  4. Leasing vanishes: the server just exits. The service will just expire after a while. Oh well...see next section

The file classifier server which uses an activatable service is



package activation;

import option3.RemoteFileClassifier;

import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lease.Lease;
import java.rmi.RMISecurityManager;
import java.rmi.MarshalledObject;
import java.rmi.activation.ActivationDesc;
import java.rmi.activation.ActivationGroupDesc;
import java.rmi.activation.ActivationGroupDesc.CommandEnvironment;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationGroup;
import java.rmi.activation.ActivationGroupID;

import java.util.Properties;

import java.rmi.activation.UnknownGroupException;
import java.rmi.activation.ActivationException;
import java.rmi.RemoteException;

/**
 * FileClassifierServer.java
 *
 *
 * Created: Wed Dec 22 1999
 *
 * @author Jan Newmarch
 * @version 1.0
 */

public class FileClassifierServer implements DiscoveryListener {

    static final protected String SECURITY_POLICY_FILE = 
	"/home/jan/projects/jini/doc/policy.all";
    // Don't forget the trailing '/'!
    static final protected String CODEBASE = "http://localhost/classes/";
    
    // protected FileClassifierImpl impl;
    protected RemoteFileClassifier stub;
    
    public static void main(String argv[]) {
	new FileClassifierServer(argv);
    }

    public FileClassifierServer(String[] argv) {
	// install suitable security manager
	System.setSecurityManager(new RMISecurityManager());

	// Install an activation group
	Properties props = new Properties();
	props.put("java.security.policy",
		SECURITY_POLICY_FILE);
	ActivationGroupDesc.CommandEnvironment ace = null;
	ActivationGroupDesc group = new ActivationGroupDesc(props, ace);
	ActivationGroupID groupID = null;
	try {
	    groupID = ActivationGroup.getSystem().registerGroup(group);
	} catch(RemoteException e) {
	    e.printStackTrace();
	    System.exit(1);
	} catch(ActivationException e) {
	    e.printStackTrace();
	    System.exit(1);
	}
	
	try {
	    ActivationGroup.createGroup(groupID, group, 0);
	} catch(ActivationException e) {
	    e.printStackTrace();
	    System.exit(1);
	}
	
	String codebase = CODEBASE;	
	MarshalledObject data = null;
	ActivationDesc desc = null;
	try {
	    desc = new ActivationDesc("activation.FileClassifierImpl",
						 codebase, data);
	} catch(ActivationException e) {
	    e.printStackTrace();
	    System.exit(1);
	}
System.out.println("Group ID " + ActivationGroup.currentGroupID().toString());

	try {
	    stub = (RemoteFileClassifier) Activatable.register(desc);
	} catch(UnknownGroupException e) {
	    e.printStackTrace();
	    System.exit(1);
	} catch(ActivationException e) {
	    e.printStackTrace();
	    System.exit(1);
	} catch(RemoteException e) {
	    e.printStackTrace();
	    System.exit(1);
	}
	
	LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
        } catch(Exception e) {
            System.err.println(e.toString());
            System.exit(1);
        }

        discover.addDiscoveryListener(this);
    }
    
    public void discovered(DiscoveryEvent evt) {

        ServiceRegistrar[] registrars = evt.getRegistrars();
	RemoteFileClassifier service;

        for (int n = 0; n < registrars.length; n++) {
            ServiceRegistrar registrar = registrars[n];

	    // export the proxy service
	    ServiceItem item = new ServiceItem(null,
					       stub,
					       null);
	    ServiceRegistration reg = null;
	    try {
		reg = registrar.register(item, Lease.FOREVER);
	    } catch(java.rmi.RemoteException e) {
		System.err.print("Register exception: ");
		e.printStackTrace();
		// System.exit(2);
		continue;
	    }
	    try {
		System.out.println("service registered at " +
				   registrar.getLocator().getHost());
	    } catch(Exception e) {
	    }	    System.out.println("exiting...");
	    System.exit(0);
	    // leaseManager.renewUntil(reg.getLease(), Lease.FOREVER, this);
	}
    }

    public void discarded(DiscoveryEvent evt) {

    }
} // FileClassifierServer

2.3 Running the Service

The service and the server must be compiled as usual, and in addition an RMI stub object must be created for the service using rmic. The class files for the stub must be copied to somewhere that an HTTP server can deliver them to clients. This is the same as for any other RMI stubs. There is an extra step that must be performed for Activatable objects: the activation server rmid must be able to reconstruct a copy of the service (the client must be able to reconstruct a copy the services's stub). This means that rmid must have access to the class files, either from an HTTP server or from the file system. In the example, the codebase property in the ActivationDesc is set to an HTTP address, so the class files for the service must be accessible to an HTTP server. The server need not be on the same machine as the service.

Before starting the server, an rmid process must be set running. on the same machine as the service. An HTTP server must be running on a machine as specified by the codebase property on the service. The server can then be started. This will register the service with rmid, and will copy a stub object up to any lookup services that are found. The server can then terminate.

While the service remains registered with lookup services, clients can download its RMI stub. The service will be created on demand by rmid. Note that the JVM for the service will be created as needed by rmid, and will be running in the same environment as rmid. In particular, such things as the current directory for the service will the same as for rmid, not from where the server ran. Similarly, the user id for the service will be the user id of rmid. This may lead to some access problems in a multiuser environment - and you should never run rmid with superuser privileges, since this will give access to your entire system! For example, a hostile service running on your machine could read the shadow password file, or worse.

3.

4.

5.

6.


This file is Copyright (©) 1999 by Jan Newmarch (http://jan.newmarch.name) jan@newmarch.name.

This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v0.4 or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.