Lease Time
In distributed applications on a network, where there may be partial failures of the network or of components, there needs to be a way for components to be timed out if they have failed, or have become unreachable. Lease is a basic mechanism GigaSpaces provides to address this problem. GigaSpaces provides this functionality using Jini technology.
Lease Basics
The essential idea behind a lease is fairly simple.
- When creating a resource, the requestor creates the resource with a limited life span.
- The grantor of the resource will then give access for some period of time that is no longer than that requested.
- The period of time that is actually granted is returned to the requestor as part of the Lease object.
- A holder of a lease can request that a Lease be renewed, or cancel the Lease at any time.
- Successfully renewing a lease extends the time period during which the lease is in effect.
- Cancelling the lease drops the lease immediately.
Few other ways Lease can be managed include,
- Specifying the Lease interface constant FOREVER, requests a lease that never expires. When granted such a lease, the grantor is responsible for ensuring that the leased resource is freed when no longer needed.
- Specifying the Lease interface constant ANY, indicates that no particular lease time is desired and that the grantor of the lease should supply a time that is most convenient for the grantor. (GigaSpaces converts leases with ANY constant to FOREVER leases).
Space Object Lease
Leases can be used for objects written to GigaSpaces cluster. All the write operations in GigaSpace interface support Lease. Lease duration is an argument that is passed to the write operations and they return a Lease Context which can be used to manage the Leases.
GigaSpace gigaSpace = new GigaSpaceConfigurer(new SpaceProxyConfigurer("space")).gigaSpace();
MyMessage message1 = new MyMessage();
// Writes the message with 1000 millis/1 sec Lease
LeaseContext<MyMessage> lease1 = gigaSpace.write(message1, 1000);
MyMessage message2 = new MyMessage();
// Writes the message with Default Lease of Lease.FOREVER
LeaseContext<MyMessage> lease2 = gigaSpace.write(message2);
MyMessage message3 = new MyMessage();
// Writes the message with Lease of Lease.FOREVER
LeaseContext<MyMessage> lease3 = gigaSpace.write(message3, Lease.FOREVER);
Getting Lease Expiration Date
You may use the Lease.getExpiration
to retrieve the time where the space object will expire. See below simple example - It writes a space into the space with 10 seconds lease and later prints how much time is left for the space object to remain in space before it will expire:
GigaSpace space = new GigaSpaceConfigurer(new EmbeddedSpaceConfigurer("space")).gigaSpace();
// Writing object into the space with 10 seconds lease time
LeaseContext<MyClass> o = space.write(new MyClass(),10000);
String UID = o.getUID();
System.out.println("Current Date:"+ new Date(System.currentTimeMillis()) + " Lease Expiration Date:" + new Date(o.getExpiration()));
while(true)
{
long expiredDue = (o.getExpiration() - System.currentTimeMillis())/1000 ;
System.out.println("Object "+UID +" Expired in :" + expiredDue+ " seconds");
if (expiredDue <= 0) break;
Thread.sleep(1000);
}
Manually Managing Space Object Lease
GigaSpaces API returns the LeaseContext
after every write operation/update operation. Space Where GigaSpaces data is stored. It is the logical cache that holds data objects in memory and might also hold them in layered in tiering. Data is hosted from multiple SoRs, consolidated as a unified data model. Object Leases can be renewed or cancelled based on the application needs.
LeaseContext<Order> lease;
...
public void writeOrder() {
...
//Save lease from write operation
lease = gigaSpace.write(singleOrder);
...
public void cancelLease() {
...
lease.cancel();
Another alternative to using LeaseContext objects is to retrieve the objects and updating the Lease to desired duration.
//Retrieve all processed low priority orders and expire the lease
Order template = new Order();
template.setType(OrderType.LOW);
template.setProcessed(true);
Order[] processedLowPriorityOrders = gigaSpace.readMultiple(template, 1000);
//Update the lease to expire in 1 second
gigaSpace.writeMultiple(processedLowPriorityOrders,
1000, // Update the Lease to 1 second
UpdateModifiers.UPDATE_OR_WRITE); // Update existing object
Managing Leases using LeaseRenewalManager
LeaseRenewalManager provides systematic renewal and management of a set of leases associated with one or more remote entities on behalf of a local entity.
Clients of the renewal manager simply give their leases to the manager and the manager renews each lease as necessary to achieve a desired expiration time (which may be later than the lease's current actual expiration time). Failures encountered while renewing a lease can optionally be reflected to the client via LeaseListener
and LeaseRenewalEvent
events.
Clients wishing to use this class must create an instance of this class in their own virtual machine to locally manage the leases granted to them. If the virtual machine that the manager was created in exits or crashes, the renewal manager will be destroyed.
LeaseListener
is invoked only when LeaseRenewalManager gets an exception in renewing the Lease
The LeaseRenewalManager distinguishes between two time values associated with lease expiration: the desired expiration time for the lease, and the actual expiration time granted when the lease is created or last renewed. The desired expiration represents when the client would like the lease to expire. The actual expiration represents when the lease is going to expire if it is not renewed. Both time values are absolute times, not relative time durations. The desired expiration time can be retrieved using the renewal manager's getExpiration
method. The actual expiration time of a lease object can be retrieved by invoking the lease's getExpiration method.
desired expiration time is absolute time and not relative time. When adding Leases to a LeaseRenewalManager, remember to pass appropriate value for desiredExpiration
parameter.
Each lease in the managed set also has two other associated attributes: a desired renewal duration, and a remaining desired duration. The desired renewal duration is specified (directly or indirectly) when the lease is added to the set. This duration must normally be a positive number; however, it may be Lease.FOREVER if the lease's desired expiration is to be unlimited. The remaining desired duration is always the desired expiration less the current time.
Each time a lease is renewed, the renewal manager will ask for an extension equal to the lease's renewal duration if the renewal duration is:
- Lease.FOREVER, or
- less than the remaining desired duration,
otherwise it will ask for an extension equal to the lease's remaining desired duration.
Lease.ANY is no longer supported.Use Lease.FOREVER when using LeaseRenewalManager.
Once a lease is given to a lease renewal manager, the manager will continue to renew the lease until one of the following occurs:
- The lease's desired or actual expiration time is reached.
- An explicit removal of the lease from the set is requested via a cancel, clear, or remove call on the renewal manager.
- The renewal manager tries to renew the lease and gets a bad object exception, bad invocation exception, or LeaseException.
Configuring LeaseRenewalManager
LeaseRenewalManager supports Configuration entries, with component "net.jini.lease.LeaseRenewalManager":
Property | Type | Default | Description |
---|---|---|---|
roundTripTime | long | 10 * 1000 // 10 seconds | The worst-case latency, expressed in milliseconds, between client and GigaSpace cluster (call to renew a lease). The value must be greater than zero. Unrealistically low values for this entry may result in failure to renew a lease. Leases managed by this manager should have durations exceeding the roundTripTime. This entry is obtained in the constructor. |
renewBatchTimeWindow | long | 5 * 60 * 1000 // 5 minutes | The maximum number of milliseconds earlier than a lease would typically be renewed to allow it to be renewed in order to permit batching its renewal with that of other leases. The value must not be negative. This entry is obtained in the constructor. |
taskManager | TaskManager | new TaskManager(11, 15000, 1.0f) | The object used to manage queuing tasks involved with renewing leases and sending notifications. The value must not be null. The default value creates a maximum of 11 threads for performing operations, waits 15 seconds before removing idle threads, and uses a load factor of 1.0 when determining whether to create a new thread. Note that the implementation of the renewal algorithm includes an assumption that the TaskManager uses a load factor of 1.0. |
Lease Renewal Algorithm
The time at which a lease is scheduled for renewal is based on the expiration time of the lease, possibly adjusted to account for the latency of the renewal call. The configuration entry roundTripTime, which defaults to ten seconds, represents the total time to make the remote call.
The following pseudo code was derived from the code which computes the renewal time. In this code, rtt represents the value of the roundTripTime:
endTime = lease.getExpiration();
delta = endTime - now;
if (delta <= rtt * 2) {
delta = rtt;
} else if (delta <= rtt * 8) {
delta /= 2;
} else if (delta <= 1000 * 60 * 60 * 24 * 7) {
delta /= 8;
} else if (delta <= 1000 * 60 * 60 * 24 * 14) {
delta = 1000 * 60 * 60 * 24;
} else {
delta = 1000 * 60 * 60 * 24 * 3;
}
renew = endTime - delta;
It is important to note that delta is never less than rtt when the renewal time is computed. A lease which would expire within this time range will be scheduled for immediate renewal.
The use of very short lease durations (at or below rtt) can cause the renewal manager to effectively ignore the lease duration and repeatedly schedule the lease for immediate renewal.
If an attempt to renew a lease fails with an indefinite exception, a renewal is rescheduled with an updated renewal time as computed by the following pseudo code:
delta = endTime - renew;
if (delta > rtt) {
if (delta <= rtt * 3) {
delta = rtt;
} else if (delta <= 1000 * 60 * 60) {
delta /= 3;
} else if (delta <= 1000 * 60 * 60 * 24) {
delta = 1000 * 60 * 30;
} else if (delta <= 1000 * 60 * 60 * 24 * 7) {
delta = 1000 * 60 * 60 * 3;
} else {
delta = 1000 * 60 * 60 * 8;
}
renew += delta;
}
Client leases are maintained in a collection sorted by descending renewal time. A renewal thread is spawned whenever the renewal time of the last lease in the collection is reached. This renewal thread examines all of the leases in the collection whose renewal time falls within renewBatchTimeWindow milliseconds of the renewal time of the last lease. If any of these leases can be batch renewed with the last lease (as determined by calling the canBatch method of the last lease) then a LeaseMap is created, all eligible leases are added to it and the LeaseMap.renewAll() method is called.
Otherwise, the last lease is renewed directly. The TaskManager that manages the renewal threads has a bound on the number of simultaneous threads it will support. The renewal time of leases may be adjusted earlier in time to reduce the likelihood that the renewal of a lease will be delayed due to exhaustion of the thread pool. Actual renewal times are determined by starting with the lease with the latest (farthest off) desired renewal time and working backwards. When computing the actual renewal time for a lease, the renewals of all leases with later renewal times, which will be initiated during the round trip time of the current lease's renewal, are considered. If using the desired renewal time for the current lease would result in more in-progress renewals than the number of threads allowed, the renewal time of the current lease is shifted earlier in time, such that the maximum number of threads is not exceeded.
Example
Following example shows a client writing Order
's to the space with a limited lease. It uses a LeaseRenewalManager to renew the Lease for the Order
. It also uses a LeaseListener which is triggered in case LeaseRenewalManager runs into errors renewing a lease. Example source can be downloaded from here.
...
public class LeaseManagerClient {
...
public LeaseManagerClient(String url) {
// use gigaspace wrapper to for simpler API
this.gigaSpace = new GigaSpaceConfigurer(new EmbeddedSpaceConfigurer("space")).gigaSpace();
createOrder();
}
private void createOrder() {
// Create new order
Order order = new Order();
order.setData("NewOrder");
order.setProcessed(false);
DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss.SSS");
Calendar calendar = Calendar.getInstance();
// Write the order with limited lease
LeaseContext<Order> lease = gigaSpace.write(order, 10000);
calendar.setTimeInMillis(System.currentTimeMillis());
logger.info("Wrote Event at: " + formatter.format(calendar.getTime()));
calendar.setTimeInMillis(lease.getExpiration());
logger.info("Object Leased upto: "
+ formatter.format(calendar.getTime()));
MyLeaseListener myListener = new MyLeaseListener();
LeaseRenewalManager renewer;
try {
//Create a LeaseRenewalManager
renewer = new LeaseRenewalManager();
//Renew the order lease for 1 minute and keep renewing every 10 seconds
renewer.renewUntil(lease, System.currentTimeMillis() + 60000,
10000, myListener);
// Get the expiration of the lease
// (this should now point to 1 minute instead of 10 seconds
long leasedUntil = renewer.getExpiration(lease);
calendar.setTimeInMillis(leasedUntil);
logger.info("LeaseRenewalManager manages lease upto: "
+ formatter.format(calendar.getTime()));
} catch (ConfigurationException e1) {
e1.printStackTrace();
logger.info("Error configuring LeaseRenewalManager");
} catch (UnknownLeaseException e) {
e.printStackTrace();
logger.info("Error retrieving Expiration");
}
}
}
public LeaseManagerClient(String url) {
... // Same as previous one
}
private void createOrder() {
... // Same as previous one
// Create a custom configuration
// (use non-default roundTripTime, default is 10 secs)
LeaseRenewalConfiguration myConfig = new LeaseRenewalConfiguration(1000);
MyLeaseListener myListener = new MyLeaseListener();
LeaseRenewalManager renewer;
try {
//Create a LeaseRenewalManager using custom config
renewer = new LeaseRenewalManager(myConfig);
//Renew the order lease for 1 minute and keep renewing every 10 seconds
renewer.renewUntil(lease, System.currentTimeMillis() + 60000,
10000, myListener);
... // Same as previous one
}
private static final class LeaseRenewalConfiguration implements Configuration {
final private Long configRTT;
public LeaseRenewalConfiguration(long renewRTT) {
configRTT = renewRTT;
}
public Object getEntry(String component, String name, Class type)
throws ConfigurationException {
return getEntry(component, name, type, -1l, null);
}
public Object getEntry(String component, String name, Class type,
Object defaultValue) throws ConfigurationException {
return getEntry(component, name, type, defaultValue, null);
}
public Object getEntry(String component, String name, Class type,
Object defaultValue, Object data) throws ConfigurationException {
if (!component.equals("net.jini.lease.LeaseRenewalManager"))
return defaultValue;
if (name.equals("roundTripTime"))
return configRTT; // renewalRTT
if (name.equals("renewBatchTimeWindow"))
return 2l; // renewBatchTimeWindow
return defaultValue;
}
}
public class MyLeaseListener implements LeaseListener{
Logger logger = Logger.getLogger(this.getClass().getName());
public MyLeaseListener ()
{
}
public void notify(LeaseRenewalEvent event) {
logger.info("LeaseRenewalEvent failed. Received the Event " + event);
}
}
public class Order implements Serializable {
private String id;
private Boolean processed;
private String data;
@SpaceId(autoGenerate=true)
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getData() {
return data;
}
public void setData(String d) {
this.data = d;
}
public Boolean getProcessed() {
return processed;
}
public void setProcessed(Boolean processed) {
this.processed = processed;
}
}
API page for LeaseRenewalManager has additional information.
Lease Expiration Notifications
Getting events once the space object lease expired can be done using the Notify Container. See example below:
SimpleListener myListenr = new SimpleListener();
SimpleNotifyEventListenerContainer notifyEventListenerContainer = new SimpleNotifyContainerConfigurer(
gigaSpace).notifyLeaseExpire(true).
template(new MyData()).eventListener(myListenr)
.notifyContainer();
The Listener
:
public class SimpleListener implements SpaceDataEventListener<MyData> {
public void onEvent(MyData data, GigaSpace gigaSpace,
TransactionStatus txStatus, Object source) {
// process event
EntryArrivedRemoteEvent event = (EntryArrivedRemoteEvent) source;
System.out.println(new Date() + " Got " +
NotifyActionType.fromModifier(event.getNotifyType())+" notification for Object "
+ data.getId() );
}
}
And with Java 8 lambda syntax:
SimpleNotifyEventListenerContainer notifyEventListenerContainer = new SimpleNotifyContainerConfigurer(space)
.template(new MyData())
.eventListener((data, gigaSpace, txStatus, source) -> {
System.out.println("Got matching event! - " + (MyData)data);
})
.notifyContainer();
or
SimpleNotifyEventListenerContainer notifyEventListenerContainer = new SimpleNotifyContainerConfigurer(space)
.template(new MyData())
.eventListener((SpaceDataEventListener<TradePojo>)(TradePojo data, GigaSpace gigaSpace1, TransactionStatus txStatus, Object source) -> {
System.out.println("Got matching event! - " + data);
})
.notifyContainer();
Notifications for expired space objects are delivered both from the primary and backup space instances. In some cases you may want to handle notifications sent only from the primary instances. The lease expiration notification example show how you can identify from which instance (primary or a backup) the lease expiration notifications has been sent.
Space Object Lease with a Persistent Space
When Objects are written to a Persistent Space (backed by a permanent store using ExternalDataSource), objects are written to the permanent store and removed from the space once lease expires. To avoid reloading the expired data into space objects should use @SpaceLeaseExpiration annotation.
Once an object's lease expires the underlying persistence store:
- will not be updated when running in
LRU Last Recently Used. This is a common caching strategy. It defines the policy to evict elements from the cache to make room for new elements when the cache is full, meaning it discards the least recently used items first.
- will not be updated when running in
All in cache
. - when running with
blobstore
(SSD or local file implementation) expired space objects are removed from theblobstore
.
Lease Manager
You can control how often this thread invokes the invalidation process. This involves iterating through all the expired space objects since the last invalidation cycle, and allowing the JVM Java Virtual Machine. A virtual machine that enables a computer to run Java programs as well as programs written in other languages that are also compiled to Java bytecode. garbage collector to release the memory consumed for the object. To configure the Lease Manager interval use the following:
space-config.lease_manager.expiration_time_interval=10000
- When writing objects into the space using a short lease time, it is recommended to configure the lease manager interval to be short.
- The
NOTIFY_LEASE_EXPIRATION
notification is called when the Lease Manager invalidates the object. - Notifications for expired objects sent both from the primary and the backup space (in case you have a backup running).
- When a transaction is timed out, its locked objects are released when the lease manager thread is triggered. This means that if a client fails while a transaction is opened with locked objects (take, write, read, and update using transactions), the locked objects are released once the lease manager thread identifies the expired transaction.