7.4. Automated Lease Renewal

Often a lease or a set of leases needs to be renewed repeatedly, either because you don't know a priori how long you'll need a particular resource, or because a space may not be willing to grant you a resource for the entire time you request. You can deal with both cases by requesting a lease for some initial length of time and then continually renewing the lease until you're finished with the resource.

This process can be automated by using a lease renewal manager. A lease renewal manager administers one or more leases, renewing them as necessary up to some specified stopping time. The lease manager pattern is common enough that the com.sun.jini.lease.LeaseRenewalManager class provides an implementation. In the remainder of this section, we are going to implement our own lease manager that is similar to (although a bit simpler than) the Jini technology lease manager. Going through the exercise of implementing a lease manager is a great way to tie together all of the concepts we've covered in this chapter, and it may also prove useful if you need to construct your own specialized lease manager.

7.4.1. The LeaseManager Interface

Our lease manager implements the following interface, which provides a set of public constructors and methods that closely mirror those of the com.sun.jini.lease.LeaseRenewalManager:

public interface LeaseManager {
  public void renewUntil(Lease lease, long stopTime,
    LeaseListener listener);

  public void renewFor(Lease lease, long duration,
    LeaseListener listener);

  public void cancel(Lease lease)
  throws UnknownLeaseException, RemoteException;
}

Two methods are supplied to add leases to the manager: renewUntil takes a lease, an absolute stop time, and a listener and adds that lease to the manager. The manager will automatically keep renewing the lease up to the stop time and will notify the listener if any renewals fail. The renewFor method is similar, except that it takes a duration time rather than an absolute stop time. Last, the cancel method takes a lease object and asks the lease manager to cancel its lease. A call to cancel throws an UnknownLeaseException if the manager doesn't know about the lease, or if the grantor of the lease no longer holds the resource, and throws a RemoteException if a communication error occurs during the lease cancellation.

7.4.2. Implementing the Constructors

We are now going to define an AutomatedLeaseManager class that implements the LeaseManager interface. We'll build the class incrementally, starting with its private fields and two constructors:

public class AutomatedLeaseManager implements LeaseManager {
  private long granularity = 1000 * 60 * 5;  // five minutes
  private Hashtable leases = new Hashtable();

  public AutomatedLeaseManager() {
    RenewThread renewer = new RenewThread();
    renewer.start();
  }

  public AutomatedLeaseManager(long granularity) {
    this();
    this.granularity = granularity;
  }

  //... methods go here
}

The lease manager has two private fields: granularity, which is set to five minutes by default, and a leases hashtable that holds the list of leases being managed. We will see how both are used throughout the lease manager code.

The lease manager provides two constructors. The first, a no-arg constructor, creates a lease manager that is ready to accept and manage any number of leases. It does so by instantiating a RenewThread object from an inner class and starting it; as we'll see in detail in Section 7.4.5, the thread takes care of renewing the leases in the hashtable at fixed time intervals. The second constructor takes a granularity—the time interval (in milliseconds) at which the lease manager regularly checks to see if any leases need to be renewed. This constructor first calls the no-arg constructor to create the RenewThread, and then assigns the granularity time to the granularity field.

7.4.3. Adding Leases

Once we've instantiated an AutomatedLeaseManager, we can place leases under its management by calling either the renewUntil or renewFor methods, which are implemented as follows:

public void renewUntil(Lease lease, long stopTime,
  LeaseListener listener)
{
  addLease(lease, stopTime, listener);
}

public void renewFor(Lease lease, long duration,
  LeaseListener listener)
{
  addLease(lease, duration +
    System.currentTimeMillis(), listener);
}

The renewUntil method takes a lease, an absolute stop time up to which the lease will be renewed, and a listener object to be informed if any renewal failures occur. The renewFor method is a slight variant: For its second parameter, it takes a duration (instead of an absolute stop time) for the lease. Both methods make use of a helper method called addLease, which does the actual work of adding the lease to the manager. The renewUntil method simply passes its parameters to the addLease method (they have the same signatures). The renewFor method first adds the current time to the duration parameter (thus translating it into an absolute stop time) before calling addLease. In effect, both renew methods add their lease parameter to a hashtable that is used to hold all the leases under the lease renewal manager's control.

Here is the implementation of the addLease method that both renew methods make use of:

private void addLease(Lease lease, long stopTime,
   LeaseListener listener)
{
   if (stopTime == Lease.ANY || stopTime == Lease.FOREVER) {
      stopTime = Long.MAX_VALUE;
   }
   leases.put(lease, new LeaseInfo(stopTime, listener));
}

The addLease method takes a lease, an absolute stop time, and a listener and adds this information to the leases hashtable (a private field in the lease manager) in the form of a key/value pair. To add the information, we first see if the stop time is either Lease.ANY or Lease.FOREVER, and in either case set the stop time to be the maximum value representable by a long (in other words, the maximum date and time representable). We then create a LeaseInfo object, which is just a wrapper object to hold the stop time and listener, and then we put this object into the hashtable with the lease as its key.

7.4.4. Cancelling a Lease

We can ask the lease manager to cancel a lease by calling the cancel method of the LeaseManager interface. Here's how we implement the method:

public void cancel(Lease lease)
  throws UnknownLeaseException, RemoteException
{
  LeaseInfo info = (LeaseInfo)leases.get(lease);
  if (info != null) {
     lease.cancel();
     leases.remove(lease);
     return;
  } else {
     throw new UnknownLeaseException();
  }
}

To cancel a lease, we first retrieve it from the leases hashtable by passing the lease to the hashtable's get method. If get finds a matching key in the hashtable, it returns a corresponding non-null LeaseInfo object; in that case we invoke the lease's cancel method and remove the lease from the hashtable. On the other hand, if get can't find a matching key, then the lease manager is not managing this lease, and we throw an UnknownLeaseException.

7.4.5. Renewing Leases

The most interesting part of the lease manager implementation is the automated lease renewal process. Recall that the AutomatedLeaseManager constructors instantiate and start a renewal thread that renews the leases stored in the hashtable at fixed time intervals. As we've mentioned, this renewal thread is instantiated from the RenewThread inner class of AutomatedLeaseManager:

private class RenewThread extends Thread {
   public RenewThread() {
     super("Renew Manager");
   }

   public void run() {
     System.out.println("RenewManager Running");
     for (;;) {
        checkLeases();
        try {
           Thread.sleep(granularity);
       } catch (InterruptedException e) {
          return; // we've been asked to stop
       }
     }
   }
}

The RenewThread extends java.lang.Thread and provides a run method that continually calls the AutomatedLeaseManager's checkLeases method, sleeping for granularity milliseconds after each call.

The checkLeases method steps through all the leases in the lease manager's hashtable. Each lease is examined to see if its stop time has passed (in which case it can be removed), or if it needs to be renewed, or if it has enough time remaining that renewal can wait until the next call to checkLeases. For each lease that needs to be renewed, we add the lease to a lease map, and then we renew them all in a batch. If errors occur in the batch renewal, we notify the respective listeners of the failed renewals. Now that we have an overview of how automated lease renewals happen, let's take a closer look at how checkLeases operates.

7.4.6. Checking Leases

The checkLeases method is defined as follows:

private void checkLeases() {
LeaseMap leasesToRenew = null;
long now = System.currentTimeMillis();

Enumeration enum = leases.keys();
while (enum.hasMoreElements()) {
  Lease lease = (Lease)enum.nextElement();
  LeaseInfo info = (LeaseInfo)leases.get(lease);

  // if lease's stop time has passed, remove it:
  if (info.stopTime < now) {
     leases.remove(lease);
     continue;
  }

// if enough time left in lease, leave it alone:
long expires = lease.getExpiration();
if (expires > (now + (granularity * 2))) {
   continue;
}

// not enough time left, so add lease to leasesToRenew  map:
if (leasesToRenew == null) {
   long duration = granularity * 2;
   leasesToRenew = lease.createLeaseMap(duration);
} else {
  if (leasesToRenew.canContainKey(lease)) {
     Long duration = new Long(granularity * 2);
     leasesToRenew.put(lease, duration);
  } else {
     info.listener.notify(
       new LeaseRenewalEvent(this,
         lease, info.stopTime,
         new LeaseException("Not batchable")));
     leases.remove(lease);
  }
 }
}

if (leasesToRenew != null) {
   try {
      leasesToRenew.renewAll();
   } catch (LeaseMapException e) {
      processExceptionMap(e.exceptionMap);
   } catch (RemoteException e) {
      ; // hope things improve next time around
   }
  }
}

Let's start at the top and work our way through this code. We first declare two local variables: leasesToRenew, a lease map that will be used to hold the leases we are going to renew, and now, a long integer initialized to the current time via the java.lang.System class's currentTimeMillis static method.

We then enumerate over each lease that is stored in the leases hashtable. For each lease, we obtain a reference to the lease and its corresponding LeaseInfo object. Then we determine what to do with the lease—remove it from the lease manager's control, or renew it for some duration, or defer doing anything till the next call to checkLeases—according to the following algorithm.

If the stop time of the lease—the time until which the lease manager should renew it—has passed, we remove the lease from the hashtable and thus remove the lease from the renewal manager's control. Then we proceed to examine the next lease in the hashtable.

If the stop time hasn't passed, then we call getExpiration on the lease to determine when the space will expire the lease. The lease may or may not have enough time left that renewal can safely wait till the next time the LeaseRenew thread checks the lease. To determine whether the lease needs to be renewed right away, we check if the remaining lease time is less than the current time plus twice the granularity. If so, then we need to renew the lease.

Let's examine what this formula means. The granularity of the lease manager controls how often leases are examined to determine which ones need to be renewed (recall that the renewal thread sleeps for granularity milliseconds in between calls to checkLeases). Here our formula for whether a lease needs to be renewed is a heuristic that says if there is less time left in the lease than two times the granularity, then we need to renew the lease (otherwise we risk that the lease will expire before our next opportunity to renew it).

If our formula determines that the lease needs to be renewed, we add the lease to the leasesToRenew lease map. If the lease map doesn't already exist, we call createLeaseMap on the lease, which creates a new map and adds the lease to it. If the lease map already exists, we first need to call canContainKey (see Section 7.3.2) to make sure the lease can be batched with the existing leases in the map. In either case, if we add the lease to the map, we add it with a duration of two times the granularity (for instance, if the granularity is five minutes, then the lease time will be ten minutes). If the lease can't be batched with the leases already in the map, then we notify the listener of the problem by throwing a LeaseRenewalEvent (described in Section 7.4.7) and remove the lease from the hashtable.

Once we've finished enumerating through all the leases, we invoke renewAll on the lease map. If all leases in the map are renewed without errors, then the checkLeases method returns. If a LeaseMapException is thrown, then a subset of the leases may have failed, and we must notify their listeners and also remove the leases from the leases hashtable; the processExceptionMap method is called to take care of both.

7.4.7. Processing Renewal Failures

The processExceptionMap method takes a Map parameter—the exception map field of a LeaseMapException that is thrown when the manager tries to renew the leases (see Figure 7.3)—which contains those leases that experienced renewal failures. Here's the code for the method:

private void processExceptionMap(Map map) {
   if (map == null) {
      return;
   }

   Enumeration enum = leases.keys();
   while (enum.hasMoreElements()) {
     Lease lease = (Lease)enum.nextElement();
     if (map.containsKey(lease)) {
        LeaseInfo info = (LeaseInfo)leases.get(lease);
        info.listener.notify(
           new LeaseRenewalEvent(this,
             lease, info.stopTime,
             (LeaseException)map.get(lease)));
        leases.remove(lease);
     }
   }
}

Since the Map interface does not support enumeration, we instead enumerate over every lease in our leases hashtable; for each lease that we find in the exception map (determined by a call to containsKey), we call get to extract its LeaseInfo object. We then notify the lease's listener that a renewal problem has occurred; the notify method of the listener will be invoked and passed a LeaseRenewalEvent. Finally, we remove the lease from the leases hashtable.

The LeaseRenewalEvent class is defined as follows:

public class LeaseRenewalEvent extends java.util.EventObject {
  private Lease lease;
  private long expiration;
  private Exception ex;

  public LeaseRenewalEvent(LeaseManager source,
    Lease lease, long expiration, Exception ex)
  {
    super(source);
    this.lease = lease;
    this.ex = ex;
  }

  public Lease getLease() {
    return lease;
  }

  public long getExpiration() {
    return expiration;
  }

  public Exception getException() {
    return ex;
  }
}

LeaseRenewalEvent is an event object that holds information about an exception that occurred. In the case of processExceptionMap, for instance, the LeaseRenewalEvent is constructed to hold the lease manager that generated the event, the lease that wasn't successfully renewed, the manager's stop time for that lease, and the exception thrown by the renewal attempt. Recall that checkLeases also makes use of a LeaseRenewalEvent, if it can't batch a particular lease with the other leases it needs to renew.

7.4.8. Putting It All Together

We've now seen the complete code for the automated lease renewal manager. There is a bit of an art to writing lease renewal managers, as the policy for when, and for how long, to renew leases is a heuristic rather than a well known algorithm. We encourage you to take the time to do Exercise 7.1, since the Jini software implementation of a lease renewal manager from Sun Microsystems, Inc. provides more sophisticated scheduling than we've shown in our example. Instead of waking up at fixed intervals, the manager makes a best guess at when it should wake up and examine the leases. It also does not follow a blanket policy for updating lease times, but instead provides a sliding scale that is dependent on the lease time—whether minutes, hours, or days—of the lease that is being renewed.

We will put our lease manager to good use later in the book; it will be one component of the collaborative application we build in Chapter 10.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset