CS 696 Emerging Technologies: Java Distributed Computing Spring Semester, 1999 Jini Leases |
||
---|---|---|
© 1999, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 15-Apr-99 |
Leases
A service that provides a resource may wish to lease the resource
Leases are used to deal with:
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.
Support for Lease Grantors
Lease Interface
canBatch(Lease lease)
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.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:
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)
Lease Example
Running The Example
The example is run like other Jini examples we have done in the class:
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.
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; } }
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; }
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 ); }
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; } }
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(); } } } }
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(); } }
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 ); } }
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(); }
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(); }
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