Leasing is the mechanism used between applications to give access to resources over a period of time in an agreed manner
Leases are requested for a period of time.
In distributed applications, there may be partial failures of the network
or of components on this network. Leasing is a way for components to
register that they are alive, but for them to be ``timed out'' if they
have failed, are unreachable, etc.
In Jini, one use of leasing
is for a service to request that a copy be kept on a lookup service
for a certain length of time for
delivery to clients on request. The service requests a time in the
ServiceRegistrar
's method register()
.
Two special values of the time are
Lease.ANY
- the service lets the lookup
service decide on the time
Lease.FOREVER
- the request is for a lease that never expires
The lookup service acts as the granter of the lease,
and decides how long it will actually create the lease for.
(The lookup service from Sun typically sets the lease time as
only five minutes.)
Once it has done that, it will attempt to ensure that the request
is honoured for that period of time. The lease is returned to the
service, and is accessible through the method getLease()
of the ServiceRegistration
object. These objects are shown
in figure 7.1
ServiceRegistration reg = registrar.register();
Lease lease = reg.getLease();
The principal methods
of the Lease
object are
package net.jini.core;
public interface Lease {
void cancel() throws
UnknownLeaseException,
java.rmi.RemoteException;
long getExpiration();
void renew(long duration) throws
LeaseDeniedException,
UnknownLeaseException,
java.rmi.RemoteException;
}
The expiration value from getExpiration()
is the time in milliseconds
since the beginning of the epoch
(the same as in System.currentTimeMillis()
).
To find the amount of time still remaining from the present, the current time
can be subtracted from this:
long duration = lease.getExpiration() - System.currentTimeMillis();
A service can cancel its lease by using cancel()
.
The lease communicates
back to the lease management system on the lookup service which
cancels storage of the service.
When a lease expires, it does so silently. That is, the lease granter
(the lookup service) will not inform the lease holder (the service)
that it has expired.
While it might seem nice to get warning of a lease expiring so that it
can be renewed, this would have to be in advance of
the expiration (``I'm just about to expire, please renew me quickly!'')
and this would probably be impractical.
Instead, it is upto the service to call renew()
before the lease expires if it wishes the lease to continue.
The servis in milliseconds.
The expiration
time is since the epoch, whereas the
duration
time is from now.
Generally leases will be renewed and the manager will function
quietly. However, the lookup service may decide not to renew a
lease and will cause an exception to be thrown. This will be caught
by the renewal manager and will cause the listener's notify()
method to be called with a LeaseRenewalEvent
as
parameter. This will allow the application to take corrective
action if its lease is denied.
The listener may be null
.
You have to be careful about setting the duration
in
renewFor()
(in Jini v1.0).
If you want the service to be registered
forever, it is tempting to use Lease.FOREVER
.
However, the Jini 1.0 implementation just adds this to
System.currentTimeMillis()
which overflows to a
negative value which is not checked. So it never does any renewals.
Strictly, you need to check that
duration + System.currentTimeMillis() > 0
before calling renewFor()
. This is fixed in Jini 1.1.
The method renewUntil()
can use
Lease.FOREVER
with no problems.
All of the above discussion was from the side of a client that receives a lease, and has to manage it. The converse of this is the agent that grants leases and has to manage things from its side. This is more advanced material which can be skipped for now: it is not needed until the chapter on ``Remote Events''. An example of use is given in the chapter on ``More Complex Examples''.
A lease can be granted for almost any remote service, any one where one object wants to maintain information about another one which is not within the same virtual machine. Being remote, there are the added partial failure modes such as network crash, remote service crash, timeouts and so on. An object will want the remote service to keep ``pinging'' it periodically to say that it is still alive and wants the information kept. Without this periodic assurance, the object may conclude that the remote service has vanished or is somehow unreachable, and it should discard the information about it.
Leases are a very general mechanism for one service to have confidence in the existence of the other for a limited period. Being general, it allows a great deal of flexibility in use. Because of the possible variety of services, some parts of the Jini lease mechanisms cannot be given totally, and must be left as interfaces for applications to fill in. The generality means that all the details are not filled in for you, as your own requirements cannot be completely predicted in advance.
A lease is given as an interface
and any agent that wishes to grant leases must implement this interface.
Jini gives two implementations, an AbstractLease
and
a subclass of this, a LandlordLease
.
A main issue in implementing a particular lease class lies in setting a policy for handling the initial request for a lease period, and in deciding what to do when a renewal request comes in. Some simple possibilities are
There are other issues, though. Any particular lease will need a time-out mechanism. A group of leases can be managed together, and this can reduce the amount of overhead of managing individual leases.
An abstract lease gives a basic implementation of a lease, that can almost be used for simple leases. Warning: this class, and those that depend on it are still not fully specified, and may change in future versions of Jini.
package com.sun.jini.lease;
public abstract class AbstractLease implements Lease, java.io.Serializable {
protected AbstractLease(long expiration);
public long getExpiration();
public int getSerialFormat();
public void setSerialFormat(int format);
public void renew(long duration);
protected abstract long doRenew(long duration);
}
This class supplies straightforward implementations of much of the
Lease
interface, with three provisos: firstly, the constructor
is protected
,
so that constructing a lease with a specified duration
is devolved to a subclass. This means that lease duration policy must be
set by this subclass. Secondly, the renew()
method calls
into the abstract doRenew()
method, again to force a
subclass to implement a renewal policy. Thirdly,
it does not implement the method
cancel()
, so that this must also be left to a subclass.
So this class implements the easy things, and leaves all matters of
policy to concrete subclasses.
The ``landlord'' is a package to allow more complex leasing systems
to be built. It is not part of the Jini specification, but is supplied
as a set of classes and interfaces.
(Warning: this is part of the com.sun.jini
package, which may change for Jini 1.1.)
The set is not complete in itself -
some parts are left as interfaces, and need to have class implementations.
These will be supplied by a particular application.
A landlord looks after a set of leases.
Leases are indentified to the landlord by a ``cookie'',
which is just some object that uniquely labels each lease to its landlord.
It could be an Integer
, for example, with a new value for
each lease. A landlord does not need to create leases itelf, as it can
use a landlord lease factory to do this. (But of course it
can create them,
depending on how an implementation is done.)
When a lease wishes to
cancel or renew itself, it asks its landlord to do it. A client is unlikely
to ask the landlord directly, as it will only have been given a lease, not
a landlord.
The principal classes and interfaces in the landlord
package are shown in figure 7.2 .
The interfaces assume that they will be implemented in certain ways, in that
each implementation class will contain a reference to certain other
interfaces. This doesn't show in the interface specifications!
If we have a landlord for, say a Foo
resource, then
we could end up with the class structure of figure 7.3.
LandlordLease
objects contain a reference to a
FooLandlord
renew()
request from a lease will get passed to a landlord.
This cannot handle it directly, since this is a matter requiring policy
decisions. It must be passed to a lease policy object. One way of doing
this (as shown in figure 7.3)
is for the landlord to have a reference to
a lease manager which has a reference to a lease policy. Similarly, a
newLease()
request from the landlord will need to invoke
a newLease()
method on the factory, and this can be
done by ensuring that the lease policy also has a reference to the factory
The class LandlordLease
extends AbstractLease
.
This has private fields cookie
and landlord
as shown in
figure 7.4.
cancel()
and doRenew()
is deferred to its landlord.
public void cancel() {
landlord.cancel(cookie);
}
protected long doRenew(long renewDuration) {
return landlord.renew(cookie, renewDuration);
}
The class can be used as is, with no subclassing needed.
Note that the landlord system produces these
leases, but does not actually keep them anywhere - they are passed
on to clients, which then use the lease to call the landlord and
hence interact with the landlord lease system. Within this system
the cookie is used as an identifier for the lease.
A LeasedResource
is a convenience wrapper around a
resource that includes extra information and methods for use
by landlords. It defines an interface
public interface LeasedResource {
public void setExpiration(long newExpiration);
public long getExpiration();
public Object getCookie();
}
This includes the ``cookie '', a unique identifier for a lease within
a landlord system as well as expiration information for the lease.
This is all the information maintained about the lease, which has been
given out to a client.
An implementation will typically include the resource that is leased, plus a method of setting the cookie. For example,
/**
* FooLeasedResource.java
*/
package foolandlord;
import com.sun.jini.lease.landlord.LeasedResource;
public class FooLeasedResource implements LeasedResource {
static protected int cookie = 0;
protected int thisCookie;
protected Foo foo;
protected long expiration = 0;
public FooLeasedResource(Foo foo) {
this.foo = foo;
thisCookie = cookie++;
}
public void setExpiration(long newExpiration) {
this.expiration = newExpiration;
}
public long getExpiration() {
return expiration;
}
public Object getCookie() {
return new Integer(thisCookie);
}
public Foo getFoo() {
return foo;
}
} // FooLeasedResource
A lease policy is used when a lease is first granted, and when it tries
to renew itself. The time requested may be granted, modified or denied.
A lease policy is specified by the LeasePolicy
interface.
package com.sun.jini.lease.landlord;
public interface LeasePolicy {
public Lease leaseFor(LeasedResource resource, long requestedDuration)
throws LeaseDeniedException;
public long renew(LeasedResource resource, long requestedDuration)
throws LeaseDeniedException, UnknownLeaseException;
public boolean ensureCurrent(LeasedResource resource);
}
This includes a ``factory'' method leaseFor()
to return a
lease based on the policy and request.
An implementation of this policy
is given by the LeaseDurationPolicy
.
This grants and renews leases based on constant
values for maximum and default lease durations.
package com.sun.jini.lease.landlord;
public class LeaseDurationPolicy implements LeasePolicy {
public LeaseDurationPolicy(long maximum, long defaultLength,
Landlord landlord, LeaseManager mgr, LandlordLeaseFactory factory);
public Lease leaseFor(LeasedResource resource, long requestedDuration)
throws LeaseDeniedException;
public long renew(LeasedResource resource, long requestedDuration);
public boolean ensureCurrent(LeasedResource resource);
}
In adddition to implementing the interface methods, the constructor also
passes in the factory to be used (which will probably be a
LandlordLease.Factory
) and maximum and default lengths for leases.
The maximum is to set a hard upper limit (which could be, say, Lease.FOREVER
),
while the default is if the client
asks for a duration of Lease.ANY
.
The operations that can be carried out on a lease are creation, renewal and
cancellation. The first two are subject to the lease policy, and must be
handled by the leaseFor()
and renewed()
methods
of the policy. This sets or alters the properties of a single lease.
There may be many leases for a resource, or even many resources with one
or more leases. Some level of management for a group of leases may be needed,
and this is done by a LeaseManager
. This defines the interface
package com.sun.jini.lease.landlord;
public interface LeaseManager {
public void register(LeasedResource resource, long duration);
public void renewed(LeasedResource resource, long duration,
long oldExpiration);
}
This doesn't actually manage the leases, since they have been given to the client.
Rather, it handles the lease resource which has the cookie
identifier and the expiration time for the lease.
An implementation of this will look after a set of leases
(really, their resources) in some manner,
by adding a new lease resource to its set and by updating information about renewals.
The interface does not include a method for informing the manager of cancelled
leases, though - that is done to the Landlord
instead,
from the lease itself when its cancel()
method is called.
This split responsibility between LeaseManager
and Landlord
is a little awkward and can possibly lead to
memory leaks with the manager holding a reference to a lease (resource) that the
landlord has cancelled. Either the list of lease resources must be shared between
the two, or the landlord must ensure that it passes on cancellations
to the manager.
There is also an issue of how the lease manager is informed of changes to
individual leases by the lease policy.The LeaseDurationPolicy
will pass on this information in its leaseFor()
and
renew()
methods, but other implementations of
LeasePolicy
need not. As we only
use this policy implementation, we are okay here.
A third issue is about who looks after leases expiring, and how this can be done. No part of the landlord specifications talk about this or give a suitable method. This suggests that it too is subject to some sort of policy, but it is not one with landlord support. It is left to implementations of one of the landlord interfaces or to a subclass. A convenient place to locate this checking is in the lease manager since it has knowledge of all the leases and their duration. Possible ways include
The FooLeaseManager
implements this third method
/**
* FooLeaseManager.java
*/
package foolandlord;
import java.util.*;
import net.jini.core.lease.Lease;
import com.sun.jini.lease.landlord.LeaseManager;
import com.sun.jini.lease.landlord.LeasedResource;
import com.sun.jini.lease.landlord.LeaseDurationPolicy;
import com.sun.jini.lease.landlord.Landlord;
import com.sun.jini.lease.landlord.LandlordLease;
import com.sun.jini.lease.landlord.LeasePolicy;
import java.util.Map;
public class FooLeaseManager implements LeaseManager {
protected static long DEFAULT_TIME = 30*1000L;
protected Vector fooResources = new Vector();
protected LeaseDurationPolicy policy;
public FooLeaseManager(Landlord landlord) {
policy = new LeaseDurationPolicy(Lease.FOREVER,
DEFAULT_TIME,
landlord,
this,
new LandlordLease.Factory());
new LeaseReaper().run();
}
public void register(LeasedResource r,long duration) {
fooResources.add(r);
}
public void renewed(LeasedResource r, long duration, long olddur) {
// no smarts in the scheduling, so do nothing
}
public Map cancelAll(Object[] cookies) {
for (int n = cookies.length; --n >= 0; ) {
cancel(cookies[n]);
}
return null;
}
public void cancel(Object cookie) {
for (int n = fooResources.size(); --n >= 0; ) {
FooLeasedResource r = (FooLeasedResource) fooResources.elementAt(n);
if (r.getCookie().equals(cookie)) {
fooResources.removeElementAt(n);
}
}
}
public LeasePolicy getPolicy() {
return policy;
}
public LeasedResource getResource(Object cookie) {
for (int n = fooResources.size(); --n >= 0; ) {
FooLeasedResource r = (FooLeasedResource) fooResources.elementAt(n);
if (r.getCookie().equals(cookie)) {
return r;
}
}
return null;
}
class LeaseReaper extends Thread {
public void run() {
while (true) {
try {
Thread.sleep(DEFAULT_TIME) ;
}
catch (InterruptedException e) {
}
for (int n = fooResources.size()-1; n >= 0; n--) {
FooLeasedResource r = (FooLeasedResource)
fooResources.elementAt(n)
;
if (!policy.ensureCurrent(r)) {
System.out.println("Lease expired for cookie = " +
r.getCookie()) ;
fooResources.removeElementAt(n);
// replace this landlord.cancel(r.getCookie()) ;
}
}
}
}
}
} // FooLeaseManager
The Landlord
is the final interface in the package that we need
for a basic landlord system. Other classes and interfaces,
such as LeaseMap
are for handling of leases in ``batches'',
and will not be dealt with here. The Landlord
interface is
package com.sun.jini.lease.landlord;
public interface Landlord extends Remote {
public long renew(Object cookie, long extension)
throws LeaseDeniedException, UnknownLeaseException, RemoteException;
public void cancel(Object cookie)
throws UnknownLeaseException, RemoteException;
public RenewResults renewAll(Object[] cookie, long[] extension)
throws RemoteException;
public void cancelAll(Object[] cookie)
throws LeaseMapException, RemoteException;
}
The renew()
and cancel()
methods are usually called
from the renew()
and cancel()
methods of a particular
lease. The renew()
method needs to use a policy object to ask
for renewal. In the FooManager
implementation it gets this
policy from the FooLeaseManager
. The cancel()
method needs to modify the list of leases. In the FooManager
implementation it passes this onto the FooLeaseManager
since that
is the only object that maintains a list of resources.
There must be a method to ask for a new lease for a resource. This is not specified by the landlord package. This request will probably be made on the lease granting side and this should have access to the landlord object, which forms a central point for lease management. So an implementation of this interface will quite likely have a method such as
public Lease newFooLease(Foo foo, long duration);
which will give a lease for a resource.
The lease used in the landlord package is a LandlordLease
.
This contains a private field, which is a reference to the landlord itself.
The lease is given to a client as a result of newFooLease()
,
and this client will usually be a remote object. This will involve
serialising the lease and sending it to this remote client. While
serialising it, the landlord field will also be serialised and sent to
the client. When the client methods such as renew()
are called,
the implementation of the LandlordLease
will make a call
to the landlord, which by then will be remote from its origin. So the
landlord object invoked by the lease will need to be a remote object
making a remote call. The Landlord
interface already extends
Remote
, but if it is to run as a remote object, then the
easiest way is for FooLandlord
to extend the
UnicastRemoteObject
class.
Putting all this together for the FooLandlord
class gives
/**
* FooLandlord.java
*/
package foolandlord;
import com.sun.jini.lease.landlord.*;
import net.jini.core.lease.LeaseDeniedException;
import net.jini.core.lease.Lease;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.Remote;
import java.util.Map;
public class FooLandlord extends UnicastRemoteObject
implements Landlord {
FooLeaseManager manager;
public FooLandlord() throws java.rmi.RemoteException {
manager = new FooLeaseManager(this);
}
public void cancel(Object cookie) {
manager.cancel(cookie);
}
public Map cancelAll(Object[] cookies) {
return manager.cancelAll(cookies);
}
public long renew(java.lang.Object cookie,
long extension)
throws net.jini.core.lease.LeaseDeniedException,
net.jini.core.lease.UnknownLeaseException {
LeasedResource resource = manager.getResource(cookie);
if (resource != null) {
return manager.getPolicy().renew(resource, extension);
}
return -1;
}
public Lease newFooLease(Foo foo, long duration)
throws LeaseDeniedException {
FooLeasedResource r = new FooLeasedResource(foo);
return manager.getPolicy().leaseFor(r, duration);
}
public Landlord.RenewResults renewAll(java.lang.Object[] cookies,
long[] extensions) {
long[] granted = new long[cookies.length];
Exception[] denied = new Exception[cookies.length];
for (int n = cookies.length; --n >= 0; ) {
try {
granted[n] = renew(cookies[n], extensions[n]);
denied[n] = null;
} catch(Exception e) {
granted[n] = -1;
denied[n] = e;
}
}
return new Landlord.RenewResults(granted, denied);
}
} // FooLandlord
Leasing allows resources to be managed without complex garbage collection mechanisms.
Leases received from services can be dealt with easily using LeaseRenewalManager
.
Entities that need to hand out leases can use a system such as the landlord
system to handle these leases.
If you found this chapter of value, the full book is available from APress or Amazon . There is a review of the book at Java Zone