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.

Our previous code examples focused on implementing activation, and arranging for objects to deactivate. We implemented the JoinManager class -- defined in the Core Jini Specification -- which the Jini service uses to handle registrations with lookup services and maintain its leases with those lookup services. JoinManager assumes responsibility for those tasks, but when the object deactivates and is destroyed, so is its JoinManager instance. While inactive, the service would fail to listen to and join newly available lookup services. In addition, it would not renew its leases, causing them to expire and making the service inaccessible to subsequent clients.

The Collection of Jini Technology Helper Utilities and Services Specifications, introduced in version 1.1. of the JSK, define three services that help solve these problems.

The lookup discovery service

Since Jini services must continuously discover and join lookup services that interest them -- and discard unavailable lookup services -- activatable services must employ third parties to fulfill those responsibilities while dormant. As its name suggests, the lookup discovery service (LDS) introduced in JSK 1.1 helps Jini services discover lookup services.

Jini services can register their interest in lookup services with the LDS, which will notify the registered Jini service when appropriate lookup services become available. According to the Jini Lookup Discovery Service Specification, "While an entity is inactive, the lookup discovery service, running on the same or a separate host, would employ the discovery protocols to find lookup services in which the entity has expressed interest, and would notify the entity when a previously unavailable lookup service has become available."

The LDS only informs the Jini service of available lookup services. The Jini service must join them, and remove its registration from stale or discarded services. Therefore, an activatable service implementation needs to initiate when the Jini service receives an LDS notification. However, the underlying assumption is that new lookup services are rarely discovered in a djinn.

To take advantage of the LDS, a Jini service must first locate an instance of the LDS via a lookup service, then register with it. The LookupDiscoveryService interface defines just one method:

public interface LookupDiscoveryService
{
   public LookupDiscoveryRegistration register
    (
     String[] groups, 
     LookupLocator[] locators, 
     RemoteEventListener listener, 
     MarshalledObject handback, 
     long leaseDuration)
  throws RemoteException;
}

The LDS performs both group and locator discovery; the register() method consumes an array of groups or lookup locators. The LDS will notify the RemoteEventListener when it discovers lookup services. The notifications are sent as RemoteDiscoveryEvent, a subclass of RemoteEvent. The LDS sends only one RemoteDiscoveryEvent to a registered client per discovery. The sequence of events generated by the LDS is strictly increasing, meaning that each event has an ID number -- consecutive events have consecutive ID numbers. If a gap occurs between event sequences, the Jini service must "sync up" with the LDS; that is, it would have to request the full set of discovered lookup services from the LDS via the LookupDiscoveryRegistration.getRegistrars() method. A Jini service can also specify an object to be included in the remote events sent by the LDS. The client can use this hand-back object to, for instance, uniquely keep track of LDSs it receives events from. The LookupDiscoveryRegistration object returned by the register() method contains a lease to the LDS. The Jini service specifies the lease duration. The registration object is mutable: the Jini service can add or remove locators and groups, or notify the LDS if it wishes to discard lookup services. It can also retrieve an array of all the lookup services the discovery service has found on its behalf.

When a "well-behaved" Jini service receives a RemoteDiscoveryEvent, it should activate and immediately join that lookup service; it should not wait until some other event, such as a client remote method call, causes it to activate. By immediately establishing residency in many lookup services, the Jini service increases its availability to clients.

The lease renewal service

Jini services lease their residency in lookup services, and must periodically renew it. In addition, as we have seen above, many other Jini services make themselves available as lessors. This presents another issue for activatable services.

According to the Jini Lease Renewal Service Specification, "The client of a leased service may run into difficulties if that service deactivates. Unless the client ensures that some other process renews the client's leases while it is inactive, or that the client is activated before its lease begins to expire, the client will lose access to the resources it has acquired. This loss can be particularly dramatic in the case of lookup service registrations. A service's registration with a lookup service is leased -- if the service deactivates ... and it does not take appropriate steps, its registrations with lookup services will expire, and before long it will be inaccessible. If that service only becomes active when clients invoke its methods, it may never become active again, because at this point new clients may not be able to find it."

The "other process" mentioned in the above paragraph is the lease renewal service (LRS), another Jini service specified in JSK 1.1. While new lookup services rarely appear in a djinn, lease renewal is much more common and would likely waste the computational resources that are managed by activation. Thus, lease renewal can be delegated to the LRS.

To use the LRS, a service must locate it with lookup services. Thereafter a client can call its createLeaseRenewalSet() method:

public interface LeaseRenewalService
{
   public LeaseRenewalSet createLeaseRenewalSet (long leaseDuration)
      throws RemoteException;
}

The LeaseRenewalSet is a way to manage leases with the LRS. The Jini service leases a renewal set from the LRS for a specific amount of time. When the lease expires, the LRS stops renewing the leases in that set. An obvious catch-22 results: once a service becomes inactive, how can it renew its lease for the LeaseRenewalSet? It must activate to do so. The Jini service can ask the LeaseRenewalSet to send it a warning event before the lease expires; it can then activate and renew the set's lease. The setExpirationWarningListener() method, of the LeaseRenewalSet class, registers the warning request.

The renewFor() method adds leases to a set; you can specify how long the lease should remain in the set, i.e., how long the LRS should keep renewing it. In addition to having methods that both register a listener for the set's lease expiration and add leases to the set, LeaseRenewalSet features methods that add and remove listeners for events that indicate a lease-renewal failure, and obtain all the set's managed leases.

To summarize, an activatable service must initiate:

  1. When it receives a LookDiscoveryEvent. At that point, it must join the located lookup services, or discard lookup services no longer available.
  2. When it receives an ExpirationWarningEvent, at which time it must renew its lease for the LeaseRenewalSet that generated the event.

Listening for events: The event mailbox service

A Jini service need not immediately handle all remote events directed to it. A dormant activatable Jini service implementation may not want to activate every time it receives a remote event. The event mailbox service (EMS), the third helper service introduced in the JSK 1.1, allows a service to delegate event listening to this third-party process. The Jini Distributed Event Specification highlights as one of its design objectives the ability to interpose third-party objects into the event-notification chain. The EMS can be used to intercept events, store them, and forward them when the mailbox's client deems it convenient.

Once a lookup service produces the EMS reference, a Jini service can register with it, resulting in a MailboxRegistration:

public interface EventMailbox
{
   MailboxRegistration register (long leaseDuration)
      throws RemoteException;
}

The MailboxRegistration object will be associated with a lease that can be obtained with the getLease() method -- and can be passed by an activatable service into the lease renewal manager's renewal set. The registration object also contains a RemoteEventListener implementation. The getListener() method returns that object, which can then be passed into any method requiring a RemoteEventListener. The EMS will intercept events directed to this listener.

The mailbox queues up the events for the original listener until the mailbox's enableDelivery(RemoteEventListener targetListener) method is called. At that time, the target event listener consumed by this method will receive the events. For instance, when an activatable service starts up, it can call enableDelivery() to receive all events accumulated while dormant, and any new events. When the service decides to deactivate, it can call the mailbox's disableDelivery() method; the EMS will then resume intercepting and storing remote events while the service is inactive.

Saving state

The Jini Specification outlines not only how a service should tend to the continuous activities outlined above, but also how to maintain a persistent state across service restarts and crashes. This persistent state consists of the following information:

  • Service ID: Across all subsequent registrations, the service must use the service ID obtained from the first lookup service it registered with.
  • Attributes associated with the service in lookup services. These attributes must be consistent for all lookup service entries.
  • Groups the service wishes to participate in.
  • Specific lookup services to register with.

When a service deactivates, this information needs to be saved to persistent storage. Likewise, when the service activates, it must be retrieved to initialize the service. Other pieces of information, specific to a service implementation, may need to be similarly saved.

In Part 1, we saw how RMI's MarshalledObject allows initialization data to pass to the service via the activation constructor, whose implementation could then set the appropriate instance variables for the service. (In the example cited in Part 1, the parameter was the directory path to a bank's property description file.) Since the activation constructor features only one MarshalledObject parameter, one option is to create a special object that encapsulates all the initialization data needed for the Jini service. You could then obtain this information from the object's instance, returned by MarshalledObject's getObject() method. A more flexible approach would be to record the name of the directory in the MarshalledObject, then store the initialization information in that directory as serialized objects.

Summary

In this article, I highlighted the benefits of RMI activation for increasing the availability of Jini services. Activation offers a tool -- managed by an activation system process -- to the programmer that allows remote object implementations to be brought into and removed from execution. First, the programmer has to make an object activatable by outfitting it with an activation constructor, and arranging for it to be exported to the RMI runtime. This is easily accomplished by subclassing the java.rmi.activation.Activatable class. Then, a developer must register objects with rmid by specifying the object's activation group descriptor and activation descriptor. For each activation group, the activation system will spawn a Java VM, which runs as its child process, and will then initiate the activatable object in that VM. An object activates via the activation constructor, which allows initialization data to be passed to it. When an object is no longer active, it notifies the activation system via a call to Activatable.inactive(). Lastly, the activation system destroys the object, and may shut down the VM for the group in which the object was running.

This infrastructure supports high availability for Jini services for the following reasons:

  • The activation system (rmid) manages computational resources, making remote object servers (servers where Jini service implementations are running) more reliable, and requiring less system administration.
  • Activatable objects preserve remote references over restarts. If a service registers its proxy with lookup services, restarting the service -- or the entire object server -- causes no problems for the proxies; their references to the service will remain valid. Without activation, these remote references would become invalid (i.e., unusable by clients).
  • The activation system manages VMs for activatable services, so if a service crashes it will be restarted (activated) automatically. Also, if a server is rebooted, as long as rmid starts up, it will automatically make every service registered with it available for remote method calls.

In most computing environments, if a software or hardware component is available, it means the component is executing. But in the Jini context, even if a component is not executing, it is still available in the Jini federation as long as it holds valid registrations in lookup services. In order to ensure that availability, an activatable Jini service must follow these guidelines for good Jini citizenship:

  • Attend to its continuous responsibilities in the Jini federation: discover and register with lookup services, manage leases, and be responsive to remote events
  • Preserve its state across service restarts, including its service ID, groups it is interested in joining, the attributes stamped on its lookup service entry, and a set of lookup locators with which it wishes to perform unicast discovery

The RMI activation framework, together with the Jini Helper Services introduced in version 1.1 of the JSK, provides the developer with the necessary tools to satisfy these requirements; this allows Jini services to be highly available. Developers will undoubtedly invent many ingenious patterns for using these, and future, tools. I would like to invite those interested in sharing ideas to contribute descriptions of use patterns that make Jini services -- implemented in hardware and software alike -- highly available in Jini federations.

Frank Sommers is the founder and CEO of Autospaces, a startup focused on bringing Jini technology to the automotive software market. He has been programming in Java since 1995, after attending the first public demonstration of the language on the Sun Microsystems campus in November of that year. His interests include parallel and distributed computing, the discovery and representation of knowledge in databases, and the philosophical foundations of computing. When not thinking about computers, he composes and plays piano, studies the symphonies of Gustav Mahler, and explores the writings of Aristotle and Ayn Rand. Sommers would like to thank John McClain of Sun Microsystems for his valuable comments on this article.

Learn more about this topic