![JAR search and dependency download from the Maven repository](/logo.png)
net.jini.lease.LeaseRenewalManager Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.jini.lease;
import org.apache.river.config.Config;
import org.apache.river.constants.ThrowableConstants;
import org.apache.river.logging.Levels;
import org.apache.river.logging.LogManager;
import org.apache.river.proxy.ConstrainableProxyUtil;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.core.lease.Lease;
import net.jini.core.lease.LeaseException;
import net.jini.core.lease.LeaseMap;
import net.jini.core.lease.LeaseMapException;
import net.jini.core.lease.UnknownLeaseException;
import org.apache.river.thread.NamedThreadFactory;
/**
* Provides for the systematic renewal and overall management of a set
* of leases associated with one or more remote entities on behalf of a
* local entity.
*
* This class removes much of the administrative burden associated with
* lease renewal. 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 LeaseRenewalEvent
instances.
*
* Note that this class is not remote. Entities 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.
*
* 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.
*
* 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.ANY
if the lease's desired expiration is
* Lease.FOREVER
. 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.ANY
, or
* - less than the remaining desired duration,
*
* otherwise it will ask for an extension equal to the lease's remaining
* desired duration.
*
* 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
.
*
*
* The methods of this class are appropriately synchronized for
* concurrent operation. Additionally, this class makes certain
* guarantees with respect to concurrency. When this class makes a
* remote call (for example, when requesting the renewal of a lease),
* any invocations made on the methods of this class will not be
* blocked. Similarly, this class makes a reentrancy guarantee with
* respect to the listener objects registered with this class. Should
* this class invoke a method on a registered listener (a local call),
* calls from that method to any other method of this class are
* guaranteed not to result in a deadlock condition.
*
* @author Sun Microsystems, Inc.
* @see Lease
* @see LeaseException
* @see LeaseRenewalEvent
*
*
*
* The following implementation-specific items are discussed below:
*
*
* Configuring LeaseRenewalManager
*
* This implementation of LeaseRenewalManager
supports the
* following configuration entries, with component
* net.jini.lease.LeaseRenewalManager
:
*
*
*
* •
*
* renewBatchTimeWindow
*
* Type: long
*
* Default: 5 * 60 * 1000 // 5 minutes
*
* Description: 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.
*
*
*
* •
*
* roundTripTime
*
* Type: long
*
* Default: 10 * 1000 // 10 seconds
*
* Description: The worst-case latency, expressed in milliseconds,
* to assume for a remote 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.
*
*
*
* •
*
* executorService
*
* Type: {@link ExecutorService}
*
* Default: new ThreadPoolExecutor(1,11,15,TimeUnit.SECONDS,
* new LinkedBlockingQueue())
*
* Description: 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.
*
*
* Logging
*
*
* This implementation uses the {@link Logger} named
* net.jini.lease.LeaseRenewalManager
to log information at
* the following logging levels:
*
*
*
* net.jini.lease.LeaseRenewalManager
*
* Level Description
*
* {@link Levels#FAILED FAILED}
* Lease renewal failure events, or leases that expire before
* reaching the desired expiration time
*
* {@link Levels#HANDLED HANDLED}
* Lease renewal attempts that produce indefinite exceptions
*
* {@link Level#FINE FINE}
* Adding and removing leases, lease renewal attempts, and desired
* lease expiration events
*
*
*
*
* For a way of using the FAILED
and HANDLED
logging
* levels in standard logging configuration files, see the {@link LogManager}
* class.
*
*
* 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 remote renewal call. The configuration entry
* roundTripTime
, which defaults to ten seconds, represents
* the total time to make the remote call.
*
* The following pseudocode 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 pseudocode:
*
*
* 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 {@link Lease#canBatch canBatch} method of
* the last lease) then a {@link LeaseMap} is created, all eligible leases
* are added to it and the {@link LeaseMap#renewAll} method is called. Otherwise, the
* last lease is renewed directly.
*
* The ExecutorService
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.
*
*/
public class LeaseRenewalManager {
private static final String LRM = "net.jini.lease.LeaseRenewalManager";
private static final Logger logger = Logger.getLogger(LRM);
/* Method objects for manipulating method constraints */
private static final Method cancelMethod;
private static final Method cancelAllMethod;
private static final Method renewMethod;
private static final Method renewAllMethod;
static {
try {
cancelMethod = Lease.class.getMethod(
"cancel", new Class[] { });
cancelAllMethod = LeaseMap.class.getMethod(
"cancelAll", new Class[] { });
renewMethod = Lease.class.getMethod(
"renew", new Class[] { long.class });
renewAllMethod = LeaseMap.class.getMethod(
"renewAll", new Class[] { });
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
/* Methods for comparing lease constraints. */
private static final Method[] leaseToLeaseMethods = {
cancelMethod, cancelMethod, renewMethod, renewMethod
};
/* Methods for converting lease constraints to lease map constraints. */
private static final Method[] leaseToLeaseMapMethods = {
cancelMethod, cancelAllMethod, renewMethod, renewAllMethod
};
private final long renewBatchTimeWindow;
/** Task manager for queuing and renewing leases
* NOTE: test failures occur with queue's that have capacity,
* no test failures occur with SynchronousQueue, for the time
* being, until the cause is sorted out we may need to rely on
* a larger pool, if necessary. TaskManager is likely to have
* lower throughput capacity that ExecutorService with a
* SynchronousQueue although this hasn't been confirmed yet.
*/
final ExecutorService leaseRenewalExecutor;
/**
* The worst-case renewal round-trip-time
*/
private final long renewalRTT ;
/**
* Entries for leases that are not actively being renewed.
* Lease with the earliest renewal is last in the map.
*/
private final SortedMap leases = new TreeMap();
/** Entries for leases that are actively being renewed */
private final List leaseInRenew = new ArrayList(1);
/** The queuer task */
private QueuerTask queuer = null;
/**
* Used to determine concurrency constraints when calculating actual
* renewals. The list is stored in a field to avoid reallocating it.
*/
private List calcList;
private final class RenewTask implements Runnable {
/** Entries of leases to renew (if multiple, all can be batched) */
private final List bList;
/**
* True if this task only holds leases that have reached their
* actual or desired expiration
*/
private final boolean noRenewals;
/**
* Create a collection of entries whose leases can be batch
* renewed with the last lease in the map, or a list of entries
* whose leases need to be removed. Which is created depends on
* the state of the last lease in the map. Remove each entry
* from the map, and add them to leaseInRenew.
*/
RenewTask(long now) {
bList = new ArrayList(1);
Entry e = (Entry) leases.lastKey();
if (e.renewalsDone() || e.endTime <= now) {
noRenewals = true;
Map lMap = leases.tailMap(new Entry(now, renewalRTT));
for (Iterator iter = lMap.values().iterator();
iter.hasNext(); )
{
Entry be = (Entry) iter.next();
if (be.renewalsDone() || be.endTime <= now) {
iter.remove();
logExpiration(be);
/*
* Only add to bList if we need to tell someone
* about this lease's departure
*/
if (be.listener != null)
bList.add(be);
}
}
} else {
noRenewals = false;
Map lMap = leases.tailMap(new Entry(e.renew + renewBatchTimeWindow, renewalRTT));
for (Iterator iter = lMap.values().iterator();
iter.hasNext(); )
{
Entry be = (Entry) iter.next();
if (be == e || be.canBatch(e)) {
iter.remove();
leaseInRenew.add(be);
bList.add(be);
}
}
}
}
@Override
public void run() {
if (noRenewals) {
// Just notify
tell(bList);
} else {
/*
* Get rid of any leases that have expired and then do
* renewals
*/
long now = System.currentTimeMillis();
List bad = processBadLeases(now);
if (!bList.isEmpty())
renewAll(bList, now);
if (bad != null)
tell(bad);
}
}
/**
* Find any expired leases, remove them from bList and
* leaseInRenew, and return any with listeners.
*/
private List processBadLeases(long now) {
List bad = null;
synchronized (LeaseRenewalManager.this) {
for (Iterator iter = bList.iterator(); iter.hasNext(); ) {
Entry e = (Entry) iter.next();
if (e.endTime <= now) {
iter.remove();
logExpiration(e);
removeLeaseInRenew(e);
if (e.listener != null) {
if (bad == null)
bad = new ArrayList(1);
bad.add(e);
}
}
}
}
return bad;
}
}
private static class Entry implements Comparable {
/*
* Since the cnt only gets modified in the constructor, and the
* constructor is always called from synchronized code, the cnt
* does not need to be synchronized.
*/
private static long cnt = 0;
/** Unique id */
public final long id;
/** The lease */
public final Lease lease;
/** Desired expiration */
public long expiration;
/** Renew duration */
public long renewDuration;
/** The listener, or null */
public final LeaseListener listener;
/** Current actual expiration */
public long endTime;
private final long renewalRTT;
/**
* The next time we have to do something with this lease.
* Usually a renewal, but could be removing it from the managed
* set because its desired expiration has been reached.
*/
public long renew;
/** Actual time to renew, given concurrency limitations */
public long actualRenew;
/** Renewal exception, or null */
public Throwable ex = null;
public Entry(Lease lease, long expiration, long renewDuration, long renewalRTT, LeaseListener listener)
{
this.endTime = lease.getExpiration();
this.lease = lease;
this.expiration = expiration;
this.renewDuration = renewDuration;
this.listener = listener;
this.renewalRTT = renewalRTT;
id = cnt++;
}
/** Create a fake entry for tailMap */
public Entry(long renew, long renewalRTT) {
this.renew = renew;
id = Long.MAX_VALUE;
lease = null;
listener = null;
this.renewalRTT = renewalRTT;
}
/**
* If the renewDuration is ANY, return ANY, otherwise return the
* minimum of the renewDuration and the time remaining until the
* desired expiration.
*/
public long getRenewDuration(long now) {
if (renewDuration == Lease.ANY)
return renewDuration;
return Math.min(expiration - now, renewDuration);
}
/** Calculate the renew time for the lease entry */
public void calcRenew(long now) {
endTime = lease.getExpiration();
if (renewalsDone()) {
if (null == desiredExpirationListener()) {
// Nothing urgent needs to be done with this lease
renew = Long.MAX_VALUE;
} else {
/*
* Tell listener about dropping this lease in a
* timely fashion
*/
renew = expiration;
}
return;
}
long delta = endTime - now;
if (delta <= renewalRTT * 2) {
delta = renewalRTT;
} else if (delta <= renewalRTT * 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;
}
/** Calculate a new renew time due to an indefinite exception */
public void delayRenew() {
long delta = endTime - renew;
if (delta <= renewalRTT) {
return;
} else if (delta <= renewalRTT * 3) {
delta = renewalRTT;
} 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;
}
/** Sort by decreasing renew time, secondary sort by decreasing id */
@Override
public int compareTo(Object obj) {
if (this == obj)
return 0;
Entry e = (Entry) obj;
if (renew < e.renew || (renew == e.renew && id < e.id))
return 1;
return -1;
}
@Override
public boolean equals(Object o){
if (!(o instanceof Entry)) return false;
Entry that = (Entry) o;
if (this.id != that.id) return false;
if (this.renew != that.renew) return false;
if (this.renewalRTT != that.renewalRTT) return false;
if (!Objects.equals(this.lease, that.lease)) return false;
return Objects.equals(this.listener, that.listener);
}
@Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + (int) (this.id ^ (this.id >>> 32));
hash = 67 * hash + (this.lease != null ? this.lease.hashCode() : 0);
hash = 67 * hash + (this.listener != null ? this.listener.hashCode() : 0);
hash = 67 * hash + (int) (this.renewalRTT ^ (this.renewalRTT >>> 32));
hash = 67 * hash + (int) (this.renew ^ (this.renew >>> 32));
return hash;
}
/**
* Returns true if the renewal of this lease can be batched with
* the (earlier) renewal of the given lease. This method must
* be called with an entry such that e.renew <= this.renew.
*
* First checks that both leases require renewal, have the same
* client constraints, and can be batched. Then enforces
* additional requirements to avoid renewing the lease too much
* more often than necessary.
*
* One of the following must be true:
*
* - This lease has a renewal duration of Lease.ANY, meaning
* it doesn't specify its renewal duration.
*
*
- The amount of time from the other lease's renewal time
* to this one's is less than half of the estimated time needed
* to perform renewals (renewalRTT). In this case, the renewal
* times are so close together that the renewal duration
* shouldn't be materially affected.
*
*
- This lease's expiration time is no more than half its
* renewal duration greater than the renewal time of the other
* lease. This case insures that this lease is not renewed
* until at least half of it's renewal duration has
* elapsed.
*
* In addition, one of the following must be true:
*
* - The other lease has a renewal duration of Lease.ANY,
* meaning we don't know how long its next renewal will be.
*
*
- The other lease is not going to be renewed again before
* this lease's renewal time, because either its next renewal
* will last until after this lease's renewal time or it will
* only be renewed once more.
*/
public boolean canBatch(Entry e) {
return (!renewalsDone() &&
!e.renewalsDone() &&
sameConstraints(lease, e.lease) &&
lease.canBatch(e.lease) &&
(renewDuration == Lease.ANY ||
renew - e.renew <= renewalRTT / 2 ||
endTime - e.renew <= renewDuration / 2) &&
(e.renewDuration == Lease.ANY ||
e.renew > renew - e.renewDuration ||
e.renew >= e.expiration - e.renewDuration));
}
/**
* Returns true if the two leases both implement RemoteMethodControl
* and have the same constraints for Lease methods, or both don't
* implement RemoteMethodControl, else returns false.
*/
private static boolean sameConstraints(Lease l1, Lease l2) {
if (!(l1 instanceof RemoteMethodControl)) {
return !(l2 instanceof RemoteMethodControl);
} else if (!(l2 instanceof RemoteMethodControl)) {
return false;
} else {
return ConstrainableProxyUtil.equivalentConstraints(
((RemoteMethodControl) l1).getConstraints(),
((RemoteMethodControl) l2).getConstraints(),
leaseToLeaseMethods);
}
}
/**
* Return the DesiredExpirationListener associated with this
* lease, or null if there is none.
*/
public DesiredExpirationListener desiredExpirationListener() {
if (listener == null)
return null;
if (listener instanceof DesiredExpirationListener)
return (DesiredExpirationListener) listener;
return null;
}
/**
* Return true if the actual expiration is greater than or equal
* to the desired expiration (e.g. we don't need to renew this
* lease any more.
*/
public boolean renewalsDone() {
return expiration <= endTime;
}
}
/**
* No-argument constructor that creates an instance of this class
* that initially manages no leases.
*/
public LeaseRenewalManager() {
this.renewBatchTimeWindow = 1000 * 60 * 5;
this.renewalRTT = 10 * 1000;
leaseRenewalExecutor =
new ThreadPoolExecutor(
1, /* min threads */
11, /* max threads */
15,
TimeUnit.SECONDS,
new SynchronousQueue(), /* Queue has no capacity */
new NamedThreadFactory("LeaseRenewalManager",false),
new CallerRunsPolicy()
);
}
private static Init init(Configuration config) throws ConfigurationException{
return new Init(config);
}
private static class Init {
long renewBatchTimeWindow = 1000 * 60 * 5 ;
long renewalRTT = 10 * 1000;
ExecutorService leaseRenewalExecutor;
Init(Configuration config) throws ConfigurationException{
if (config == null) {
throw new NullPointerException("config is null");
}
renewBatchTimeWindow = Config.getLongEntry(
config, LRM, "renewBatchTimeWindow",
renewBatchTimeWindow, 0, Long.MAX_VALUE);
renewalRTT = Config.getLongEntry(
config, LRM, "roundTripTime",
renewalRTT, 1, Long.MAX_VALUE);
leaseRenewalExecutor = Config.getNonNullEntry(
config,
LRM,
"executorService",
ExecutorService.class,
new ThreadPoolExecutor(
1, /* Min Threads */
11, /* Max Threads */
15,
TimeUnit.SECONDS,
new SynchronousQueue(), /* No capacity */
new NamedThreadFactory("LeaseRenewalManager",false),
new CallerRunsPolicy()
)
);
}
}
/**
* Constructs an instance of this class that initially manages no leases
* and that uses config
to control implementation-specific
* details of the behavior of the instance created.
*
* @param config supplies entries that control the configuration of this
* instance
* @throws ConfigurationException if a problem occurs when obtaining
* entries from the configuration
* @throws NullPointerException if the configuration is null
*/
public LeaseRenewalManager(Configuration config)
throws ConfigurationException
{
this(init(config));
}
private LeaseRenewalManager(Init init){
this.renewBatchTimeWindow = init.renewBatchTimeWindow;
this.renewalRTT = init.renewalRTT;
this.leaseRenewalExecutor = init.leaseRenewalExecutor;
}
/**
* Constructs an instance of this class that will initially manage a
* single lease. Employing this form of the constructor is
* equivalent to invoking the no-argument form of the constructor
* followed by an invocation of the three-argument form of the
* renewUntil
method. See renewUntil
for
* details on the arguments and what exceptions may be thrown by
* this constructor.
*
* @param lease reference to the initial lease to manage
* @param desiredExpiration the desired expiration for
* lease
* @param listener reference to the LeaseListener
* object that will receive notifications of any exceptional
* conditions that occur during renewal attempts. If
* null
no notifications will be sent.
* @throws NullPointerException if lease
is
* null
* @see LeaseListener
* @see #renewUntil
*/
public LeaseRenewalManager(Lease lease,
long desiredExpiration,
LeaseListener listener)
{
this.renewBatchTimeWindow = 1000 * 60 * 5;
this.renewalRTT = 10 * 1000;
leaseRenewalExecutor = new ThreadPoolExecutor(
1, /* Min Threads */
11, /* Max Threads */
15,
TimeUnit.SECONDS,
new SynchronousQueue(), /* No Capacity */
new NamedThreadFactory("LeaseRenewalManager",false),
new CallerRunsPolicy()
);
renewUntil(lease, desiredExpiration, listener);
}
/**
* Include a lease in the managed set until a specified time.
*
* If desiredExpiration
is Lease.ANY
* calling this method is equivalent the following call:
*
* renewUntil(lease, Lease.FOREVER, Lease.ANY, listener)
*
* otherwise it is equivalent to this call:
*
* renewUntil(lease, desiredExpiration, Lease.FOREVER, listener)
*
*
* @param lease the Lease
to be managed
* @param desiredExpiration when the client wants the lease to
* expire, in milliseconds since the beginning of the epoch
* @param listener reference to the LeaseListener
* object that will receive notifications of any exceptional
* conditions that occur during renewal attempts. If
* null
no notifications will be sent.
* @throws NullPointerException if lease
is
* null
* @see #renewUntil
*/
public final void renewUntil(Lease lease,
long desiredExpiration,
LeaseListener listener)
{
if (desiredExpiration == Lease.ANY) {
renewUntil(lease, Lease.FOREVER, Lease.ANY, listener);
} else {
renewUntil(lease, desiredExpiration, Lease.FOREVER, listener);
}
}
/**
* Include a lease in the managed set until a specified time and
* with a specified renewal duration.
*
* This method takes as arguments: a reference to the lease to
* manage, the desired expiration time of the lease, the renewal
* duration time for the lease, and a reference to the
* LeaseListener
object that will receive notification
* of exceptional conditions when attempting to renew this
* lease. The LeaseListener
argument may be
* null
.
*
* If the lease
argument is null
, a
* NullPointerException
will be thrown. If the
* desiredExpiration
argument is
* Lease.FOREVER
, the renewDuration
* argument may be Lease.ANY
or any positive value;
* otherwise, the renewDuration
argument must be a
* positive value. If the renewDuration
argument does
* not meet these requirements, an
* IllegalArgumentException
will be thrown.
*
* If the lease passed to this method is already in the set of
* managed leases, the listener object, the desired expiration, and
* the renewal duration associated with that lease will be replaced
* with the new listener, desired expiration, and renewal duration.
*
* The lease will remain in the set 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
.
*
*
* This method will interpret the value of the
* desiredExpiration
argument as the desired absolute
* system time after which the lease is no longer valid. This
* argument provides the ability to indicate an expiration time that
* extends beyond the actual expiration of the lease. If the value
* passed for this argument does indeed extend beyond the lease's
* actual expiration time, then the lease will be systematically
* renewed at appropriate times until one of the conditions listed
* above occurs. If the value is less than or equal to the actual
* expiration time, nothing will be done to modify the time when the
* lease actually expires. That is, the lease will not be renewed
* with an expiration time that is less than the actual expiration
* time of the lease at the time of the call.
*
* If the LeaseListener
argument is a
* non-null
object reference, it will receive
* notification of exceptional conditions occurring upon a renewal
* attempt of the lease. In particular, exceptional conditions
* include the reception of a LeaseException
, bad
* object exception, or bad invocation exception (collectively these
* are referred to as definite exceptions) during a renewal
* attempt or the lease's actual expiration being reached before its
* desired expiration.
*
* If a definite exception occurs during a lease renewal request,
* the exception will be wrapped in an instance of the
* LeaseRenewalEvent
class and sent to the listener.
*
* If an indefinite exception occurs during a renewal request for
* the lease, renewal requests will continue to be made for that
* lease until: the lease is renewed successfully, a renewal attempt
* results in a definite exception, or the lease's actual expiration
* time has been exceeded. If the lease cannot be successfully
* renewed before its actual expiration is reached, the exception
* associated with the most recent renewal attempt will be wrapped
* in an instance of the LeaseRenewalEvent
class and
* sent to the listener.
*
* If the lease's actual expiration is reached before the lease's
* desired expiration time, and either 1) the last renewal attempt
* succeeded or 2) there have been no renewal attempts, a
* LeaseRenewalEvent
containing a null
* exception will be sent to the listener.
*
* @param lease the Lease
to be managed
* @param desiredExpiration when the client wants the lease to
* expire, in milliseconds since the beginning of the epoch
* @param renewDuration the renewal duration to associate with the
* lease, in milliseconds
* @param listener reference to the LeaseListener
* object that will receive notifications of any exceptional
* conditions that occur during renewal attempts. If
* null
, no notifications will be sent.
* @throws NullPointerException if lease
is
* null
* @throws IllegalArgumentException if renewDuration
is
* invalid
* @see LeaseRenewalEvent
* @see LeaseException
*/
public void renewUntil(Lease lease,
long desiredExpiration,
long renewDuration,
LeaseListener listener)
{
validateDuration(renewDuration, desiredExpiration == Lease.FOREVER,
"desiredExpiration");
addLease(lease, desiredExpiration, renewDuration, listener,
System.currentTimeMillis());
}
/**
* Include a lease in the managed set for a specified duration.
*
* Calling this method is equivalent the following call:
*
* renewFor(lease, desiredDuration, Lease.FOREVER, listener)
*
*
* @param lease reference to the new lease to manage
* @param desiredDuration the desired duration (relative time) that
* the caller wants lease
to be valid for, in
* milliseconds
* @param listener reference to the LeaseListener
* object that will receive notifications of any exceptional
* conditions that occur during renewal attempts. If
* null
, no notifications will be sent.
* @throws NullPointerException if lease
is
* null
* @see #renewFor
*/
public void renewFor(Lease lease, long desiredDuration,
LeaseListener listener)
{
renewFor(lease, desiredDuration, Lease.FOREVER, listener);
}
/**
* Include a lease in the managed set for a specified duration and
* with specified renewal duration.
*
* The semantics of this method are similar to those of the
* four-argument form of renewUntil
, with
* desiredDuration
+ current time being used for the
* value of the desiredExpiration
argument of
* renewUntil
. The only exception to this is that, in
* the context of renewFor
, the value of the
* renewDuration
argument may only be
* Lease.ANY
if the value of the
* desiredDuration
argument is exactly
* Lease.FOREVER.
*
* This method tests for arithmetic overflow in the desired
* expiration time computed from the value of
* desiredDuration
argument
* (desiredDuration
+ current time). Should such
* overflow be present, a value of Lease.FOREVER
is
* used to represent the lease's desired expiration time.
*
* @param lease reference to the new lease to manage
* @param desiredDuration the desired duration (relative time) that
* the caller wants lease
to be valid for, in
* milliseconds
* @param renewDuration the renewal duration to associate with the
* lease, in milliseconds
* @param listener reference to the LeaseListener
* object that will receive notifications of any exceptional
* conditions that occur during renewal attempts. If
* null
, no notifications will be sent.
* @throws NullPointerException if lease
is
* null
* @throws IllegalArgumentException if renewDuration
is
* invalid
* @see #renewUntil
*/
public void renewFor(Lease lease,
long desiredDuration,
long renewDuration,
LeaseListener listener)
{
/*
* Validate before calculating effective desiredExpiration, if
* they want a renewDuration of Lease.ANY, desiredDuration has
* to be exactly Lease.FOREVER
*/
validateDuration(renewDuration, desiredDuration == Lease.FOREVER,
"desiredDuration");
long now = System.currentTimeMillis();
long desiredExpiration;
if (desiredDuration < Lease.FOREVER - now) { // check overflow.
desiredExpiration = now + desiredDuration;
} else {
desiredExpiration = Lease.FOREVER;
}
addLease(lease, desiredExpiration, renewDuration, listener, now);
}
/**
* Error checking function that ensures renewDuration is valid taking
* into account the whether or not the desired expiration/duration is
* Lease.FOREVER. Throws an appropriate IllegalArgumentException if
* an invalid renewDuration is passed.
*
* @param renewDuration renew duration the clients wants
* @param isForever should be true if client asked for a desired
* expiration/duration of exactly Lease.FOREVER
* @param name name of the desired expiration/duration field, used
* to construct exception
* @throws IllegalArgumentException if renewDuration is invalid
*/
private void validateDuration(long renewDuration, boolean isForever,
String name)
{
if (renewDuration <= 0 &&
!(renewDuration == Lease.ANY && isForever))
{
/*
* A negative renew duration and is not lease.ANY with a
* forever desired expiration
*/
if (renewDuration == Lease.ANY) {
/*
* Must have been Lease.ANY with a non-FOREVER desired
* expiration
*/
throw new IllegalArgumentException("A renewDuration of " +
"Lease.ANY can only be used with a " + name + " of " +
"Lease.FOREVER");
}
if (isForever) {
// Must have been a non-Lease.ANY, non-positive renewDuration
throw new IllegalArgumentException("When " + name + " is " +
"Lease.FOREVER the only valid values for renewDuration " +
"are a positive number, Lease.ANY, or Lease.FOREVER");
}
/*
* Must be a non-positive renewDuration with a non-Forever
* desired expiration
*/
throw new IllegalArgumentException("When the " + name +
" is not Lease.FOREVER the only valid values for " +
"renewDuration are a positive number or Lease.FOREVER");
}
}
private synchronized void addLease(Lease lease,
long desiredExpiration,
long renewDuration,
LeaseListener listener,
long now)
{
Entry e = findEntryDo(lease);
if (e != null && !removeLeaseInRenew(e))
leases.remove(e);
insertEntry(new Entry(lease, desiredExpiration, renewDuration, renewalRTT,
listener),
now);
calcActualRenews(now);
logger.log(Level.FINE, "Added lease {0}", lease);
}
/** Calculate the preferred renew time, and put in the map */
private void insertEntry(Entry e, long now) {
e.calcRenew(now);
leases.put(e, e);
}
/**
* Returns the current desired expiration time associated with a
* particular lease, (not the actual expiration that was granted
* when the lease was created or last renewed).
*
* @param lease the lease the caller wants the current desired
* expiration for
* @return a long
value corresponding to the current
* desired expiration time associated with lease
* @throws UnknownLeaseException if the lease passed to this method
* is not in the set of managed leases
* @see UnknownLeaseException
* @see #setExpiration
*/
public synchronized long getExpiration(Lease lease)
throws UnknownLeaseException
{
return findEntry(lease).expiration;
}
/**
* Replaces the current desired expiration of a given lease from the
* managed set with a new desired expiration time.
*
* Note that an invocation of this method with a lease that is
* currently a member of the managed set is equivalent to an
* invocation of the renewUntil
method with the lease's
* current listener as that method's listener
* argument. Specifically, if the value of the
* expiration
argument is less than or equal to the
* lease's current desired expiration, this method takes no action.
*
* @param lease the lease whose desired expiration time should be
* replaced
* @param expiration long
value representing the new
* desired expiration time for the lease
* argument
* @throws UnknownLeaseException if the lease passed to this method
* is not in the set of managed leases
* @see #renewUntil
* @see UnknownLeaseException
* @see #getExpiration
*/
public synchronized void setExpiration(Lease lease, long expiration)
throws UnknownLeaseException
{
Entry e = findEntry(lease);
e.expiration = expiration;
if (expiration != Lease.FOREVER && e.renewDuration == Lease.ANY)
e.renewDuration = Lease.FOREVER;
if (leaseInRenew.indexOf(e) < 0) {
leases.remove(e);
long now = System.currentTimeMillis();
insertEntry(e, now);
calcActualRenews(now);
}
}
/**
* Removes a given lease from the managed set, and cancels it.
*
* Note that even if an exception is thrown as a result of the
* cancel operation, the lease will still have been removed from the
* set of leases managed by this class. Additionally, any exception
* thrown by the cancel
method of the lease object
* itself may also be thrown by this method.
*
* @param lease the lease to remove and cancel
* @throws UnknownLeaseException if the lease passed to this method
* is not in the set of managed leases
* @throws RemoteException typically, this exception occurs when
* there is a communication failure between the client and
* the server. When this exception does occur, the lease may
* or may not have been successfully cancelled, (but the
* lease is guaranteed to have been removed from the managed
* set).
* @see Lease#cancel
* @see UnknownLeaseException
*/
public void cancel(Lease lease)
throws UnknownLeaseException, RemoteException
{
remove(lease);
lease.cancel();
}
public void close(){
leaseRenewalExecutor.shutdown();
}
/**
* Removes a given lease from the managed set of leases; but does
* not cancel the given lease.
*
* @param lease the lease to remove from the managed set
* @throws UnknownLeaseException if the lease passed to this method
* is not in the set of managed leases
* @see UnknownLeaseException
*/
public synchronized void remove(Lease lease) throws UnknownLeaseException {
Entry e = findEntry(lease);
if (!removeLeaseInRenew(e))
leases.remove(e);
calcActualRenews();
logger.log(Level.FINE, "Removed lease {0}", lease);
}
/**
* Removes all leases from the managed set of leases. This method
* does not request the cancellation of the removed leases.
*/
public synchronized void clear() {
leases.clear();
leaseInRenew.clear();
calcActualRenews();
logger.log(Level.FINE, "Removed all leases");
}
/** Calculate the actual renew times, and poke/restart the queuer */
private void calcActualRenews() {
calcActualRenews(System.currentTimeMillis());
}
/** Calculate the actual renew times, and poke/restart the queuer */
private void calcActualRenews(long now) {
/*
* Subtract one to account for the queuer thread, which should not be
* counted.
*/
int maxThreads = leaseRenewalExecutor instanceof ThreadPoolExecutor ?
((ThreadPoolExecutor)leaseRenewalExecutor).getMaximumPoolSize() - 1
: 10;
if (calcList == null) {
calcList = new ArrayList(maxThreads);
}
for (Iterator iter = leases.values().iterator(); iter.hasNext(); ) {
Entry e = (Entry) iter.next();
// Start by assuming we can renew the lease when we want
e.actualRenew = e.renew;
if (e.renewalsDone()) {
/*
* The lease's actual expiration is >= desired
* expiration, drop the lease if the desired expiration
* has been reached and we don't have to tell anyone
* about it
*/
if (now >= e.expiration &&
e.desiredExpirationListener() == null)
{
logExpiration(e);
iter.remove();
}
/*
* Even if we have to send an event we assume that it
* won't consume a slot in our schedule
*/
continue;
}
if (e.endTime <= now && e.listener == null) {
// Lease has expired and no listener, just remove it.
logExpiration(e);
iter.remove();
continue;
}
/*
* Make sure there aren't too many lease renewal threads
* operating at the same time.
*/
if (!canBatch(e)) {
/*
* Find all renewals that start before we expect ours to
* be done.
*/
for (Iterator listIter = calcList.iterator();
listIter.hasNext(); )
{
if (e.renew >=
((Entry) listIter.next()).actualRenew - renewalRTT)
{
/*
* This renewal starts after we expect ours to
* be done.
*/
break;
}
listIter.remove();
}
if (calcList.size() == maxThreads) {
/*
* Too many renewals. Move our actual renewal time
* earlier so we'll probably be done before the last
* one needs to start. Remove that one, since it
* won't overlap any earlier renewals.
*/
Entry e1 = (Entry) calcList.remove(0);
e.actualRenew = e1.actualRenew - renewalRTT;
}
calcList.add(e);
}
}
calcList.clear();
long newWakeup = wakeupTime();
if (queuer == null) {
if (newWakeup < Long.MAX_VALUE) {
queuer = new QueuerTask(newWakeup);
leaseRenewalExecutor.execute(queuer);
}
} else if (newWakeup < queuer.wakeup ||
(newWakeup == Long.MAX_VALUE && leaseInRenew.isEmpty()))
{
notifyAll();
}
}
/**
* Return true if e can be batched with another entry that expires
* between e.renew - renewBatchTimeWindow and e.renew.
*/
private boolean canBatch(Entry e) {
Iterator iter = leases.tailMap(e).values().iterator();
iter.next(); // skip e itself
while (iter.hasNext()) {
Entry be = (Entry) iter.next();
if (e.renew - be.renew > renewBatchTimeWindow)
break;
if (e.canBatch(be))
return true;
}
return false;
}
/**
* Find a lease entry, throw exception if not found or expired
* normally
*/
private Entry findEntry(Lease lease) throws UnknownLeaseException {
Entry e = findEntryDo(lease);
if (e != null &&
(e.renew < e.endTime || System.currentTimeMillis() < e.endTime))
{
return e;
}
throw new UnknownLeaseException();
}
/** Find a lease entry, or null */
private Entry findEntryDo(Lease lease) {
Entry e = findLeaseFromIterator(leases.values().iterator(), lease);
if (e == null)
e = findLeaseFromIterator(leaseInRenew.iterator(), lease);
return e;
}
/** Find a lease entry, or null */
private static Entry findLeaseFromIterator(Iterator iter, Lease lease) {
while (iter.hasNext()) {
Entry e = (Entry) iter.next();
if (e.lease.equals(lease))
return e;
}
return null;
}
/** Notify the listener for each lease */
private void tell(List bad) {
for (Iterator iter = bad.iterator(); iter.hasNext(); ) {
Entry e = (Entry) iter.next();
if (e.renewalsDone()) {
final DesiredExpirationListener del =
e.desiredExpirationListener();
if (del != null) {
del.expirationReached(new LeaseRenewalEvent(this, e.lease,
e.expiration, null));
}
continue;
}
e.listener.notify(new LeaseRenewalEvent(this, e.lease,
e.expiration, e.ex));
}
}
/**
* Logs a lease expiration, distinguishing between expected
* and premature expirations.
*
* @param e the Entry
holding the lease
*/
private void logExpiration(Entry e) {
if (e.renewalsDone()) {
logger.log(Level.FINE,
"Reached desired expiration for lease {0}",
e.lease);
} else {
logger.log(Levels.FAILED,
"Lease '{'0'}' expired before reaching desired expiration of {0}",
e.expiration);
}
}
/**
* Logs a throw. Use this method to log a throw when the log message needs
* parameters.
*
* @param level the log level
* @param sourceMethod name of the method where throw occurred
* @param msg log message
* @param params log message parameters
* @param e exception thrown
*/
private static void logThrow(Level level,
String sourceMethod,
String msg,
Object[] params,
Throwable e)
{
LogRecord r = new LogRecord(level, msg);
r.setLoggerName(logger.getName());
r.setSourceClassName(LeaseRenewalManager.class.getName());
r.setSourceMethodName(sourceMethod);
r.setParameters(params);
r.setThrown(e);
logger.log(r);
}
/** Renew all of the leases (if multiple, all can be batched) */
private void renewAll(List bList, long now) {
Map lmeMap = null;
Throwable t = null;
List bad = null;
try {
if (bList.size() == 1) {
Entry e = (Entry) bList.get(0);
logger.log(Level.FINE, "Renewing lease {0}", e.lease);
e.lease.renew(e.getRenewDuration(now));
} else {
LeaseMap batchLeaseMap = createBatchLeaseMap(bList, now);
logger.log(Level.FINE, "Renewing leases {0}", batchLeaseMap);
batchLeaseMap.renewAll();
}
} catch (LeaseMapException ex) {
lmeMap = ex.exceptionMap;
bad = new ArrayList(lmeMap.size());
} catch (Throwable ex) {
t = ex;
bad = new ArrayList(bList.size()); // They may all be bad
}
/*
* For each lease we tried to renew determine the associated
* exception (if any), and then ether add the lease back to
* leases (if the renewal was successful), schedule a retry and
* add back to leases (if the renewal was indefinite), or drop
* the lease (by not adding it back to leases) and notify any
* interested listeners. In any event remove lease from the
* list of leases being renewed.
*/
now = System.currentTimeMillis();
synchronized (this) {
for (Iterator iter = bList.iterator(); iter.hasNext(); ) {
Entry e = (Entry) iter.next();
if (!removeLeaseInRenew(e))
continue;
// Update the entries exception field
if (bad == null) {
e.ex = null;
} else {
e.ex = (t != null) ? t : (Throwable) lmeMap.get(e.lease);
}
if (e.ex == null) {
// No problems just put back in list
insertEntry(e, now);
continue;
}
/*
* Some sort of problem. If definite don't put back
* into leases and setup to notify the appropriate
* listener, if indefinite schedule for a retry and put
* back into leases
*/
final int cat = ThrowableConstants.retryable(e.ex);
if (cat == ThrowableConstants.INDEFINITE) {
e.delayRenew();
leases.put(e, e);
if (logger.isLoggable(Levels.HANDLED)) {
logThrow(
Levels.HANDLED, "renewAll",
"Indefinite exception while renewing lease {0}",
new Object[] { e.lease }, e.ex);
}
} else {
if (logger.isLoggable(Levels.FAILED)) {
logThrow(Levels.FAILED, "renewAll",
"Lease renewal failed for lease {0}",
new Object[] { e.lease }, e.ex);
}
if (e.listener != null) {
/*
* Note: For us ThrowableConstants.UNCATEGORIZED ==
* definite
*/
bad.add(e);
}
}
}
calcActualRenews(now);
}
if (bad != null)
tell(bad);
}
/** Create a LeaseMap for batch renewal */
private static LeaseMap createBatchLeaseMap(List bList, long now) {
Iterator iter = bList.iterator();
Entry e = (Entry) iter.next();
LeaseMap batchLeaseMap =
e.lease.createLeaseMap(e.getRenewDuration(now));
if (e.lease instanceof RemoteMethodControl &&
batchLeaseMap instanceof RemoteMethodControl)
{
batchLeaseMap = (LeaseMap)
((RemoteMethodControl) batchLeaseMap).setConstraints(
ConstrainableProxyUtil.translateConstraints(
((RemoteMethodControl) e.lease).getConstraints(),
leaseToLeaseMapMethods));
}
while (iter.hasNext()) {
e = (Entry) iter.next();
batchLeaseMap.put(e.lease, Long.valueOf(e.getRenewDuration(now)));
}
return batchLeaseMap;
}
/** Remove from leaseInRenew, return true if removed */
private boolean removeLeaseInRenew(Entry e) {
int index = leaseInRenew.indexOf(e); // avoid iterator cons
if (index < 0)
return false;
leaseInRenew.remove(index);
return true;
}
/** Return the soonest actual renewal time */
private long wakeupTime() {
if (leases.isEmpty())
return Long.MAX_VALUE;
return ((Entry) leases.lastKey()).actualRenew;
}
private class QueuerTask implements Runnable {
/** When to next wake up and queue a new renew task */
private long wakeup;
QueuerTask(long wakeup) {
this.wakeup = wakeup;
}
public void run() {
synchronized (LeaseRenewalManager.this) {
try {
while (true) {
wakeup = wakeupTime();
if (wakeup == Long.MAX_VALUE && leaseInRenew.isEmpty())
break;
final long now = System.currentTimeMillis();
long delta = wakeup - now;
if (delta <= 0) {
leaseRenewalExecutor.execute(new RenewTask(now));
} else {
LeaseRenewalManager.this.wait(delta);
}
}
} catch (InterruptedException ex) {
}
queuer = null;
}
}
}
}