Activatable Jini services, Part 2: Patterns of use

The RMI activation framework can produce self-sufficient and flexible Jini services

In Part 1 of this series, I defined activation as the process of creating and exporting an object via the RMI activation system, making it available for remote method calls. I also presented the chief goal of the RMI activation system: to help remote objects manage their computational resources. The JDK's activation mechanism does just that; it helps objects manage their computational resources, it does not do it for them. It is up to the developer of Jini services to properly utilize RMI activation to support resource management. In Part 2, I will present the consequences of Jini activation and some helpful patterns of use. The first important area to consider is deactivation.

TEXTBOX:

TEXTBOX_HEAD: Activatable Jini services: Read the whole series!

:END_TEXTBOX

When to deactivate

An object informs the activation group that it is no longer active by calling Activatable.inactive(), which essentially calls the current activation group's inactiveObject() method. If no remote method calls to the object are pending -- i.e., the object really is inactive -- the group unexports the object and notifies its monitor. Thereafter, the activation monitor will consider the object's remote references as stale, causing activate() requests with the object's activation ID to trigger the activation protocol (described in Part 1).

If Activatable.inactive() succeeds, and if the group has no more active objects and is not activating objects, the activation system will shut down the group's Java VM. If an object fails to call Activatable.inactive(), it will never be garbage-collected because the activation group holds strong references to every object it creates. (Hence, if that object is the last one running in the Java VM, the Java VM will not shut down either.) The rules for garbage collection still hold, though: the object needs to make sure that it has no nondaemon threads running, and that it nulls out enough references to be eligible for garbage collection.

The remote object decides when to call inactive(); this decision warrants some stipulations. One requirement for calling this method is that the remote object should not handle remote method calls. Also, reactivating the object should not require more effort than keeping it active for long periods of time. For instance, if a server only runs a few activatable Jini service implementations -- each of which might receive a remote method call once every few hours -- and the server is loaded lightly, running those objects continually would be the best option.

The last deactivation issue we'll consider is leases. In the Jini programming model, services typically lease their resources. For instance, not only does LoanApprovalService lease its residency in lookup services, but clients that wish to use this service lease their access to it as well. When first contacted by a client, the service should return a Registration object that contains a lease and the remote service reference, not a direct remote reference to itself.

Suppose that our LoanApprovalService implementation runs inside a bank for the benefit of loan officers, and the employees' work schedule begins at 9 a.m. and ends at 5 p.m. In this scenario, the LoanApprovalService might decide to deactivate after 5 p.m., even if it holds leases to clients. Those leases will expire when the services reinitiate at 9 a.m.

If the service knows that many leases will expire soon, it might delay deactivation if someone will soon call it to renew those leases. For instance, if the LoanApprovalService implementation had 100 outstanding leases to clients, all due to expire shortly after 5 p.m., the service might want to postpone deactivation in anticipation of renewal requests for those leases. I'll discuss shortly how a service can delegate lease renewal.

The service should also not deactivate if it must be available at a certain time. Currently, no mechanism exists in rmid to send a wake-up call to a service. A service can arrange for another service to wake it up by making a remote call to trigger activation, but that service must be awake at the given time. Thus, in a Jini federation, at least one service must remain awake if other services need periodic notifications. We'll see an example of this later in the LeaseRenewalService.

When a service does not need to be available for longer time periods, it can decide to deactivate. If the service holds references to numerous objects, which could hold references to even more objects, the object tree takes up significant computational space -- signaling an appropriate time to deactivate.

Recall our LoanApprovalService example from Part 1: it utilized Bank objects to represent a bank's loan approval criteria, essentially representing a decision-support system. Imagine an implementation with several hundred, even thousands, of Bank objects, each of which might hold many other resources. For instance, each might open socket connections to remote hosts and database management systems (via JDBC, for example) to obtain information necessary to approve or reject a loan. (Decision-support systems typically access many data sources concurrently.) When running the loan-approval object, many thousands of other objects could run, and several hundred database connections might need to be kept open.

In such situations, the programmer weighs the alternatives: keep the objects active and continuously occupy computational resources, or stop and start the services as needed. Actions like starting up objects and opening database connections always incur overhead. For instance, opening a database connection might take several seconds. Therefore, deciding the proper criteria for deactivating an object is complex, and discussing it in general terms might lead one down a slippery slope.

A side note: Properly deploying activatable services might require techniques that minimize the startup latency of objects, even the Java VM. Some specialized Java VMs, like Sun Labs's orthogonally persistent Java implementation, PJama, might prove especially useful in this regard. Everything in PJama is persistent by default, including all objects and even threads. When a PJama VM shuts down, it persistently saves its execution state (including the threads' execution states). When that VM initiates, it typically recreates its execution state from disk much faster than if all the objects and threads had to start up anew.

The RMI activation API provides for these specialized VMs' use, since the ActivationGroupDesc class allows you to specify the Java VM executable. If you designate the PJama VM for an activation group, PJama will persist all objects running in that group and recreate them at the VM's startup. If the PJama VM significantly reduces a large object tree's startup latency, the scales will tip toward deactivating the service more often. At the other end of the spectrum are some just-in-time compilers that cause additional startup latency by compiling Java byte code to native codes for faster execution.

A service must clean up after itself before signaling that it is inactive, as does any Java object that wishes to be garbage-collected. Developers should not depend on Object's finalize() method and expect it to clean up all finite nonmemory resources (such as socket connections and references to open files). As Bill Venners points out in "Cleaning up after Jini Services," (JavaWorld, March 2000): "Don't design your Java programs such that correctness depends upon 'timely' finalization." Before calling inactive(), a service must reach a final state, meaning the object has released all nonmemory resources and is eligible for garbage collection. Therefore, inactive means clients aren't accessing the service, and the service has reached its final state (has released all nonmemory resources).

To illustrate object deactivation, we will use a simple, and somewhat contrived, criterion: we will deactivate the object if it has no outstanding leases and no remote call was made to it within N seconds. This approach might make sense if a large number of rarely used services register with an activation system. RemoteLoanServiceImpl will mark the last remote call to its requestLoan() method. It will also start a separate thread that wakes up every N seconds to check for any remote calls made since its last wake-up. If there are none, our service will inform the RMI runtime that the object is inactive. The activation group will then unexport the object and remove all strong references to it. If no other references to the object remain, it is eligible for garbage collection. Because this object is the only one running inside the activation group, the activation system will shut down the VM. Subsequent method calls will then reactivate the group -- creating the VM -- and the group will activate the object. (See the activation protocol in Part 1.)

In the following code segment, the object's constructor fires up a thread that monitors usage. This thread wakes up after secondsToStayAlive number of seconds and checks if a special flag is set to true, which indicates that requestLoan() received a method call. We extended requestLoan to set this flag each time it is called. If the flag's value is false, the usage monitoring thread calls inactive(). We do not include provisions for releasing resources, since resources should be properly managed independent of activation (i.e., they should be released when not used), and we want to keep this example simple.

private Bank decisionSystem;
int secondsToStayActive = 15;
boolean calledMethod = false;
protected ActivationID id = null;
//hold outstanding leases granted by this service
private Map leases;
//Activation constructor. When the object is activated, we'll start
//a thread to monitor usage.
public ActivatableLoanService(ActivationID id, MarshalledObject data) throws RemoteException
{
   super(id, 0);
   try {
      //init object state here
      String path = (String)data.get();
      decisionSystem = initBank(path);
      this.id = id;
      startThread();
   } catch (Exception e) {
      throw new RemoteException("Problem in activation constructor: ", e);
   }
}
//Return a registration to the service. This is the proper way for clients
//to obtain a reference to the service implementation.
public LoanServiceRegistration register(Object clientRef, long duration)
{
   //grant lease to client and save this info
   //in the map of outstanding leases
   //LoanServiceRegistration will contain a remote reference to this
   //service, as well as the Lease granted by it
   ...
}
/**
* Implementation of requestLoan() sets a flag at each use. 
*/
public LoanApproval requestLoan(LoanRequest request) throws RemoteException
{
   System.out.println("got method call");
   calledMethod = true;
   return decisionSystem.decide(request);
}
/**
* This will deactivate the object if it did not receive a method call
* since the thread's last wake up.
*/
private void startThread() 
{
   Runnable runnable = new Runnable() {
   public void run() {
      for (;;) {
         try {
            Thread.sleep(secondsToStayActive * 1000);
            synchronized(ActivatableLoanService.this) {
               if (!calledMethod && leases.isEmpty()) {
                  Activatable.inactive(id);
               } else {
                  //reset flag
                  calledMethod = false;
               }
            }
         } catch (Exception  e) {
         }
      }
   }
};
new Thread(runnable).start();
...

Good Jini citizenship

In the code above, we saw how the Jini programming model, together with the API and infrastructure, creates a robust, reliable computing environment. In order to preserve that environment's integrity, Jini services must follow certain implementation guidelines, or patterns, which together are labeled "good lookup citizenship."

According to the Jini Specification, "To be a usable service, the service implementation must register with appropriate lookup services. In other words, it must be a good lookup citizen, which means:"

  • "When starting, discovering lookup services of appropriate groups and registering with any that reply"
  • "When running, listening for lookup service 'here-I-am' messages and, after filtering by group, registering with any new ones"
  • "Remembering its join configuration -- the list of groups it should join and the lookup locators for specific lookup services"
  • "Remembering all attributes stamped on it and informing all lookups of changes in those attributes"
  • "Maintaining leases in lookup services for as long as the service is available"
  • "Remembering the service ID assigned to the service by the first lookup service, so that all registrations of the same service, no matter when made, will be under the same service ID"

Since these requirements relate to startup as well as continuous activities, an activatable Jini service must:

  1. Discover and join lookup services while dormant
  2. Renew its leases with those lookup services while dormant
  3. Handle remote events directed to it while dormant
  4. Properly renew and recreate its state at service restarts

I will discuss later how activatable Jini services can meet these requirements.

1 2 3 Page 1
Page 1 of 3