SDSU CS 696 Emerging Technologies: Java Distributed Computing
Spring Semester, 1999
Jini Leases
Previous    Lecture Notes Index    Next    
© 1999, All Rights Reserved, SDSU & Roger Whitney
San Diego State University -- This page last updated 15-Apr-99

Contents of Doc 24, Jini Leases


References

Jini on-line API Documentation

Jini source code

Doc 24, Jini Leases Slide # 2

Leases


A service that provides a resource may wish to lease the resource

Leases are used to deal with:



Some terms
Lease holder – the object that asks for a leased resource from a service

Lease grantor – the object granting access to a resource for some period of time

The lookup service is a lease grantor. It basically renews all leases when requested. This is the proper policy for the lookup service, but is not the proper policy for all leased resources. We need to cover how services can create and manage leases for their resources.

Doc 24, Jini Leases Slide # 3

Support for Lease Holders


The LeaseRenewalManager can be used by lease holders to help manage and renew leases until they are done with the resource. We have seen examples of the use the LeaseRenewalManager already.

com.sun.jini.lease.LeaseRenewalManagerMethods
cancel(Lease lease)
clear()
getExpiration(Lease lease)
remove(Lease lease)
renewFor(Lease lease, long duration, LeaseListener listener)
renewUntil(Lease lease, long expiration, LeaseListener listener)
setExpiration(Lease lease, long expiration)

In Jini 1.0 there is a bug in renewFor(). If duration + System.currentTimeMillis() overflows (that is becomes a negative value) then the lease is not renewed. So you need to check that If duration + System.currentTimeMillis() > 0. The over flow is a problem when using duration = Lease.FOREVER = Long.MAX_VALUE.

Doc 24, Jini Leases Slide # 4

Support for Lease Grantors

Lease Interface

canBatch(Lease lease)
can parameter be batched with the current lease.
cancel()
LeaseMap createLeaseMap(long duration)
Creates a Map for batching leases, and adds the current lease to the map.
getExpiration()
getSerialFormat()
renew(long duration)
Request to renew a lease. If renewal is granted duration, in milliseconds, plus current time gives new lease expiration time
setSerialFormat(int format)
format = Lease.DURATION or Lease.ABSOLUTE, how the lease expiration time will be stored in serialized form of lease

com.sun.jini.lease.AbstractLease
Abstract class that implements some of the Lease methods. A base class for implementing lease objects.

doRenew(long duration)
getExpiration()
getSerialFormat()
renew(long duration)
setSerialFormat(int format)


Doc 24, Jini Leases Slide # 5

LandlordLease System


The landlord lease system in Jini contains a number of classes and interface to help manage leases for the lease grantor and lease holder. The classes and interfaces are in the package com.sun.jini.lease.landlord.

Classes and Interfaces

Classes
Interfaces
LandlordLease
Landlord
LandlordLease.Factory
LeasedResource
Landlord.RenewResults
LeaseManager
LandlordLeaseMap
LeasePolicy

LandlordLease


A concrete class that implements the lease interface. This class is serializable. The lease grantor creates a LandlordLease object and sends a serialized version of the object to the lease holder. The lease grantor provides the LandlordLease object with a unique ID and a reference to a Landlord object. The Jini API refers to the unique ID as a cookie. It can be any serializable object. When the lease holder requests a lease to be renewed (by sending the renew() message to the LandlordLease object), the LandlordLease object sends the message renew( leaseCookie, requestedDuration ) to the Landlord object. The Landlord object then either returns the duration amount it granted or throws an exception if it does not extend the lease. Requests for canceling a lease are processes similarly.

Warning about LandlordLease objects and its getExpiration method. After the lease holder requests a renewal of a lease, the getExpiration() method returns the new expiration of the lease. If the lease grantor keeps a copy of the lease, it is a different object than the lease holder has. Hence lease grantor’s copy of the Landlordlease will not return the new expiration of the lease.


Doc 24, Jini Leases Slide # 6

LandlordLease.Factory

You must use the LandlordLease.Factory to create LandlordLease objects. The factory has one method:

newLease(java.lang.Object cookie, Landlord landlord, long duration)
The cookie is the unique id for the lease. The cookie just needs to be unique for the landlord handling the lease, as the landlord needs to identify the lease via the cookie. So lease holders could have multiple leases with the same cookie as long as the leases have different landlords. The landlord object is the object that will handle requests to renew or cancel the lease. The duration is the initial duration of the lease.

Cookie


This object is uses as an id for a lease.

There are two restrictions on this object:

Since cookie is shared between VMs and passed each time a lease is renewed or cancelled serializable seems to make more sense. A small object that has it serializable ID set should be a lot faster to transmit and use on the lease grantors side.


LandlordLeaseMap


If a lease holder has more than one lease from the same landlord, it can use a LandlordLeaseMap to help manage these leases. A LandlordLeaseMap is a map of leases. The LandlordLeaseMap put(key, value) method will only allow keys of object of type LandlordLease that are from the same landlord object. The value must be of type Long. It throws the exception IllegalArgumentException if this is not the case. The lease holder can renew or cancel all leases in a LandlordLeaseMap with methods renewAll and cancelAll. If the landlord does not renew all leases, the LandlordLeaseMap removes the leases that were not renewed from itself and then throws a net.jini.core.lease.LeaseMapException. This exception will contain a map of all leases that were not renewed.

Use the createLeaseMap( duration ) method of the Landlord lease object to create a LandlordLeaseMap. The duration value is used as the duration requested for lease renewal when the method renewAll is called.

Doc 24, Jini Leases Slide # 7

Landlord.RenewResults


When the lease holder uses a LandlordLeaseMap to manage a group of leases, the lease map requests all leases to be renewed at the same time. The landlord then returns Landlord.RenewResults indicating which leases were renewed and which were denied.

Landlord Interface


This interface is implemented by a class that will manage Landlord leases for a lease grantor. The methods cancel and renew are called be landlord lease objects. The methods cancelAll and renewAll are called by LandlordLeaseMaps. See the example later in the document for an example of implementing this interface.

Methods
cancel(java.lang.Object cookie)
cancelAll(java.lang.Object[] cookie)
renew(java.lang.Object cookie, long extension)
renewAll(java.lang.Object[] cookie, long[] extension)

LeasedResource Interface


This interface is implemented by a class that is a leased resource. It just contains the cookie for the lease and the expiration of the lease. Basically it is the information the lease grantor needs to maintain about a lease. The interface is used by the interfaces LeaseDurationPolicy and LeaseManager

Methods
getCookie()
getExpiration()
setExpiration(long newExpiration)

Doc 24, Jini Leases Slide # 8

Lease Example

In the example a client obtains a leased counter object from the server. The counter is a remote, so it runs on the server side.

Example Classes/Interfaces

Counter
Remote interface for the counter.
CounterImplementation
Implements counter interface and LeasedResource interface.
CounterAccess
A remote interface defining how client request a counter.
CounterService
Implements CounterAccess interface and Landlord interface. Note both CounterAccess and Landlord are remote interfaces.
RegisterCounterService
Registers a CounterService object with a Jini Lookup service
CounterClient
A client that connects to a CounterService object and gets a Counter reference.


Doc 24, Jini Leases Slide # 9
Running The Example

The example is run like other Jini examples we have done in the class:
There was a problem generating the stubs. Even though jini-core.jar and sun-util.jar have been make standard extensions to jdk 1.2, rmic could not find Jini related classes imported in for CounterService and CounterImplementation. To generate the stub, give rmic a classpath that includes the jar files. For example:

rmic -classpath .:/opt/jini1_0/lib/jini-core.jar:/opt/jini1_0/lib/sun-util.jar:/opt/jdk1.2/jre/lib/rt.jar CounterImplementation

Doc 24, Jini Leases Slide # 10

Source Code

Counter

package whitney.jini.examples.lease;
import java.rmi.Remote;
import java.rmi.RemoteException;
import net.jini.core.lease.Lease;
public interface Counter extends Remote
   {
   public abstract void reset() throws RemoteException;
   public abstract int increase() throws RemoteException;
   public abstract int decrease() throws RemoteException;
   public abstract int count()  throws RemoteException;
   public abstract Lease getLease() throws RemoteException;
   }

CounterAccess

package whitney.jini.examples.lease;
import java.rmi.Remote;
import java.rmi.RemoteException;
import net.jini.core.lease.LeaseDeniedException;
public interface CounterAccess extends Remote
   {
   public abstract Counter getCounter(  ) throws 
      RemoteException, LeaseDeniedException;
   }
Note. When a client request a counter from the server, the server has to return a counter and a lease. In this solution, the lease is returned inside of the counter object. This means that a single counter can not be accessed by two clients, since each client needs a different lease. It might make more sense to return a container object that contains the counter and the lease. This would require two more classes. Since I was trying to keep the example short I did not use a container class. This was probably a mistake.


Doc 24, Jini Leases Slide # 11

CounterImplementation

package whitney.jini.examples.lease;
import com.sun.jini.lease.landlord.LeasedResource;
import net.jini.core.lease.Lease;
public class CounterImplementation implements Counter {
   private long expirationTime; // millseconds from beginning of the epoch
   private Lease counterLease;
   private Object leaseCookie;
   private int count;
   public CounterImplementation(  Lease newLease, Object cookie ) {
      this( newLease, cookie, 0);
   }
   
   public CounterImplementation(Lease newLease, Object cookie, int initialCount){
      count = initialCount;
      counterLease = newLease;
      expirationTime = newLease.getExpiration();
      leaseCookie = cookie;
   }
   
   public String toString() {
      return "Counter(ID " + leaseCookie + ", count "  + count + ")";
   }
      
   // Count interface methods
   public void reset()  { count = 0; }
   public int increase() { return ++count; }
   public int decrease() { return --count; }
   public int count() { return count; }
   public Lease getLease() { return counterLease; }
      
   // LeasedResource interface methods
   public Object getCookie() { return leaseCookie; }
   public long getExpiration()  { return expirationTime; }
   
   public void setExpiration(long newExpiration)  { 
      expirationTime = newExpiration;
   } 
}

Doc 24, Jini Leases Slide # 12

CounterService

package whitney.jini.examples.lease;
import com.sun.jini.lease.landlord.Landlord;
import com.sun.jini.lease.landlord.LandlordLease.Factory;
import com.sun.jini.lease.landlord.LandlordLease;
import com.sun.jini.lease.landlord.LandlordLeaseFactory;
import com.sun.jini.lease.landlord.LeasedResource;
import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.jini.core.lease.Lease;
import net.jini.core.lease.LeaseDeniedException;
public class CounterService extends Thread implements CounterAccess, Landlord
   {
   private static final long maxLeaseDuration = 1000 * 60 * 2; //2 minutes
   // Used to create a unique ID (cookie) for each lease created. Each counter has
   // its own lease
   private static int countersCreated = 0;
   // Stores the counters, using the lease cookie as the key for the counter
   Map activeCounters = Collections.synchronizedMap( new HashMap());
   
   // CounterAccess method
   public Counter getCounter(  ) throws LeaseDeniedException, RemoteException{
      Integer leaseCookie = new Integer( countersCreated++ );
      LandlordLeaseFactory counterLeaseFactory = new LandlordLease.Factory();
      Lease counterLease = 
         counterLeaseFactory.newLease( leaseCookie, this, 
            maxLeaseDuration + now() );
      CounterImplementation newCounter = 
         new CounterImplementation(counterLease, leaseCookie );
      activeCounters.put( leaseCookie, newCounter );
      // Since CounterImplementation is not subclass of UnicastRemoteObject or
      //  Activatable, it must be exported
      Counter stub =(Counter) UnicastRemoteObject.exportObject( newCounter );
      return stub;
      }

Doc 24, Jini Leases Slide # 13
CounterService Continued
   //Landlord methods
   // Cancel is called by the LandlordLease object when client calls the cancel
   // method in the LandlordLease cancel() method
   public void cancel(java.lang.Object cookie) {
      if ( !activeCounters.containsKey( cookie ) )
         return;
      
      synchronized ( activeCounters ) {
         unexportCounterAt( cookie );
         activeCounters.remove( cookie );
      }
   }
   
   // Called by LandlordLeaseMap cancelAll method to cancel multiple leases
   public void cancelAll(java.lang.Object[] cookie) {
      for ( int k = 0; k < cookie.length; k++ )
         cancel( cookie[k] );
   }
   // Called by LandlordLeaseMap renewAll method to renew multiple leases
   public Landlord.RenewResults renewAll(java.lang.Object[] cookie, 
         long[] extension) {
      long renewalGranted[] = new long[ cookie.length ];
      Exception renewalDenied[] = new Exception[ cookie.length ];
      
      for ( int k = 0; k < cookie.length; k++ )
         try {
            renewalGranted[k] = renew( cookie[k], extension[k] );
            renewalDenied[k] = null;
         } catch (LeaseDeniedException deny ) {
            renewalGranted[k] = -1;
            renewalDenied[k] = deny;
         }
      return new Landlord.RenewResults(renewalGranted, renewalDenied );
   }

Doc 24, Jini Leases Slide # 14
CounterService Continued
   // Called by renew() method in the LandlordLease object. Server denies the
   // extension by throwning an exception
   public long renew(java.lang.Object cookie, long extension) 
      throws LeaseDeniedException {
      synchronized ( activeCounters ) {
         if ( !activeCounters.containsKey( cookie ) )
            throw new LeaseDeniedException( 
               "Requested resourse does not exist");
         // An arbitrary policy to show how to deny the renewal request
         if (activeCounters.size() > 3 )
            throw new LeaseDeniedException( 
               "Too many counters currently exist, try later");
         // Client's may request a longer extension than the server can grant
         long newDuration = Math.min( extension, maxLeaseDuration );
         
         // Record the new lease expiration time
         LeasedResource aCounter = (LeasedResource) 
            activeCounters.get( cookie);
         aCounter.setExpiration( newDuration + now() );
         // Return the duration (not expiration time) to the LandlordLease object
         return newDuration;
      }
   }

Doc 24, Jini Leases Slide # 15
CounterService Continued
   // Reap all counters with expired leases
   public void run() {
      try {
         while (true) {
            Thread.sleep( maxLeaseDuration/2 );
            reapExpiredLeases();
         }
      } catch (InterruptedException threadInterupted ) {
         // Explicitly release all resources before we exit
         destroyAllCounters();
      }
   }
   
   private void destroyAllCounters() {
      synchronized ( activeCounters ) {
         Iterator counterKeys = activeCounters.keySet().iterator();
         while (counterKeys.hasNext() ) {
            Object key = counterKeys.next();
            unexportCounterAt( key );
            counterKeys.remove();
         }
      }
   }
   private void reapExpiredLeases() {
      synchronized ( activeCounters ) {
         Iterator counterKeys = activeCounters.keySet().iterator();
         while (counterKeys.hasNext() ) {
            Object key = counterKeys.next();
            if ( hasLeaseExpired( key ) ) {
               unexportCounterAt( key );
               counterKeys.remove();
            }
         }
      }
   }

Doc 24, Jini Leases Slide # 16
CounterService Continued
   // Since counters are remotes, they are listening on a port. Unexport
   //  the remote, so it does not listen on port and can be garbage collected
   private void unexportCounterAt( Object counterKey ){
      CounterImplementation canceledCounter = 
         (CounterImplementation) activeCounters.get( counterKey );
      try {   
         UnicastRemoteObject.unexportObject( canceledCounter, true );
      } catch (NoSuchObjectException  unexportProblem ) {
         // if it does not exist don't worry about removing it
         System.err.println( "Counter " + canceledCounter + 
            " count not be unexported when canceled");
      }
   }
   private boolean hasLeaseExpired( Object cookie ) {
      LeasedResource aCounter = (LeasedResource) activeCounters.get( cookie);
      long expiration =  aCounter.getExpiration( );
      if ( now() >= expiration  )
         return true;
      else
         return false;
   }
   
   private long now() {
      return System.currentTimeMillis();
   }
}

Doc 24, Jini Leases Slide # 17

RegisterCounterService

package whitney.jini.examples.lease;
import com.sun.jini.lease.LeaseRenewalManager;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;
import net.jini.core.discovery.LookupLocator;    
import net.jini.core.entry.Entry;
import net.jini.core.lease.Lease;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistrar; 
import net.jini.core.lookup.ServiceRegistration;
import net.jini.lookup.entry.Name; 
public class RegisterCounterService {
   public static void main (String[] args) throws Exception {
      System.setSecurityManager (new RMISecurityManager ());
      LookupLocator lookup = new LookupLocator ("jini://eli.sdsu.edu");
      ServiceRegistrar registrar = lookup.getRegistrar ();
      
      Entry[] serverAttributes = new Entry[1];
      serverAttributes[0] = new Name ("CounterService");
      ServiceID serverID = null;
      CounterService theServer = new CounterService();
      theServer.start();
      CounterAccess serverStub = (CounterAccess) 
         UnicastRemoteObject.exportObject( theServer );
      ServiceItem encryptServer =  
         new ServiceItem( serverID, serverStub, serverAttributes);
      ServiceRegistration serverReg =
         registrar.register(encryptServer, 1000 * 60 * 5 );
   
      Lease serverLease = serverReg.getLease();
      LeaseRenewalManager manageLease = 
         new LeaseRenewalManager( serverLease, Lease.FOREVER, null );
      
      Thread.sleep( 1000 * 60 * 7 );
      // Server is just for testing, kill it to save the effort of manual killing it
      manageLease.cancel(serverLease); //just to be polite
      UnicastRemoteObject.unexportObject( theServer, true );
      System.exit( 0 );
      }
   }

Doc 24, Jini Leases Slide # 18

CounterClient

package whitney.jini.examples.lease;

import com.sun.jini.lease.LeaseListener;
import com.sun.jini.lease.LeaseRenewalEvent;
import com.sun.jini.lease.LeaseRenewalManager;
import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import net.jini.core.discovery.LookupLocator;    
import net.jini.core.entry.Entry;
import net.jini.core.lease.Lease;
import net.jini.core.lease.LeaseDeniedException;
import net.jini.core.lookup.ServiceRegistrar; 
import net.jini.core.lookup.ServiceTemplate; 
import net.jini.lookup.entry.Name; 
public class CounterClient extends Thread implements LeaseListener {
   Counter myCount;
   String name;
   Lease countLease;
   
   public static void main (String[] args) throws Exception {
      if (args.length == 0 ) {
         System.out.println( "Usage: java CounterClient clientName");
         System.exit(0);
      }
      CounterClient aClient =
         new CounterClient(args[0], "jini://eli.sdsu.edu", "CounterService" );
      aClient.start();
      }
   
   public CounterClient(String name, String lookupService, String serviceName) 
      throws IOException, RemoteException, ClassNotFoundException, 
         LeaseDeniedException {
      this.name = name;
      System.setSecurityManager (new RMISecurityManager ());
      LookupLocator lookup = new LookupLocator ( lookupService );
      ServiceRegistrar registrar = lookup.getRegistrar ();
      
      Entry[] serverAttributes = new Entry[1];
      serverAttributes[0] = new Name ( serviceName );
      ServiceTemplate template = 
         new ServiceTemplate (null, null, serverAttributes);
      CounterAccess countServer = (CounterAccess) registrar.lookup (template);
      myCount = (Counter) countServer.getCounter();
      countLease = myCount.getLease();
      }

Doc 24, Jini Leases Slide # 19
CounterClient Continued
   public void run() {
      while ( isLeaseValid() ) {
         try {
            myCount.increase();
            System.out.println( "CounterClient " + name + " count is: " 
               + myCount.count() );
            if ( myCount.count() > 3 )
               break;
            sleep( 1000 * 15 );
            countLease.renew( 1000 * 60 * 5 );
            System.out.println( "Lease duration: " + leaseDuration() );
         } catch (LeaseDeniedException denied ) {
            System.out.println( "CounterClient " + name + 
                  " lease renewal denied. Reason give: " +
               denied.getMessage() );
            System.out.println(  "\t still have " + (leaseDuration() / 1000) + 
               " seconds on my current lease" );
         } catch (Exception threadKilled ) {
            System.out.println("CounterClient " + name + 
               "is exiting on exception: " + threadKilled.getMessage() );
            break;
         }
      }
      
      try {
         System.out.println( "CounterClient " + name + " is doing down" );
         // be polite and cancel the lease, the cancel forces another try-catch
         countLease.cancel();
      } catch (Exception dontCareProgramExitsNow) { }
   }
   private long leaseDuration() {
      return countLease.getExpiration() - System.currentTimeMillis();
   }

Doc 24, Jini Leases Slide # 20
CounterClient Contiued
   private boolean isLeaseValid() {
      if (leaseDuration() > 0 )
         return true;
      else
         return false;
   }
}

Copyright ©, All rights reserved.
1999 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
OpenContent license defines the copyright on this document.

Previous    visitors since 11-Apr-99    Next