View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package net.jini.lease;
20  
21  import org.apache.river.config.Config;
22  import org.apache.river.constants.ThrowableConstants;
23  import org.apache.river.logging.Levels;
24  import org.apache.river.logging.LogManager;
25  import org.apache.river.proxy.ConstrainableProxyUtil;
26  import java.lang.reflect.Method;
27  import java.rmi.RemoteException;
28  import java.util.ArrayList;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Objects;
33  import java.util.SortedMap;
34  import java.util.TreeMap;
35  import java.util.concurrent.ExecutorService;
36  import java.util.concurrent.SynchronousQueue;
37  import java.util.concurrent.ThreadPoolExecutor;
38  import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
39  import java.util.concurrent.TimeUnit;
40  import java.util.logging.Level;
41  import java.util.logging.LogRecord;
42  import java.util.logging.Logger;
43  import net.jini.config.Configuration;
44  import net.jini.config.ConfigurationException;
45  import net.jini.core.constraint.RemoteMethodControl;
46  import net.jini.core.lease.Lease;
47  import net.jini.core.lease.LeaseException;
48  import net.jini.core.lease.LeaseMap;
49  import net.jini.core.lease.LeaseMapException;
50  import net.jini.core.lease.UnknownLeaseException;
51  import org.apache.river.thread.NamedThreadFactory;
52  
53  /**
54   * Provides for the systematic renewal and overall management of a set
55   * of leases associated with one or more remote entities on behalf of a
56   * local entity.
57   * <p>
58   * This class removes much of the administrative burden associated with
59   * lease renewal. Clients of the renewal manager simply give their
60   * leases to the manager and the manager renews each lease as necessary
61   * to achieve a <em>desired expiration</em> time (which may be later
62   * than the lease's current <em>actual expiration</em> time). Failures
63   * encountered while renewing a lease can optionally be reflected to the
64   * client via <code>LeaseRenewalEvent</code> instances.
65   * <p>
66   * Note that this class is not remote. Entities wishing to use this
67   * class must create an instance of this class in their own virtual
68   * machine to locally manage the leases granted to them. If the virtual
69   * machine that the manager was created in exits or crashes, the renewal
70   * manager will be destroyed.
71   * <p>
72   * The <code>LeaseRenewalManager</code> distinguishes between two time
73   * values associated with lease expiration: the <em>desired
74   * expiration</em> time for the lease, and the <em>actual
75   * expiration</em> time granted when the lease is created or last
76   * renewed. The desired expiration represents when the client would like
77   * the lease to expire. The actual expiration represents when the lease
78   * is going to expire if it is not renewed. Both time values are
79   * absolute times, not relative time durations. The desired expiration
80   * time can be retrieved using the renewal manager's
81   * <code>getExpiration</code> method. The actual expiration time of a
82   * lease object can be retrieved by invoking the lease's
83   * <code>getExpiration</code> method.
84   * <p>
85   * Each lease in the managed set also has two other associated
86   * attributes: a desired <em>renewal duration</em>, and a <em>remaining
87   * desired duration</em>. The desired renewal duration is specified
88   * (directly or indirectly) when the lease is added to the set. This
89   * duration must normally be a positive number; however, it may be
90   * <code>Lease.ANY</code> if the lease's desired expiration is
91   * <code>Lease.FOREVER</code>. The remaining desired duration is always
92   * the desired expiration less the current time.
93   * <p>
94   * Each time a lease is renewed, the renewal manager will ask for an
95   * extension equal to the lease's renewal duration if the renewal
96   * duration is:
97   * <ul>
98   * <li> <code>Lease.ANY</code>, or 
99   * <li> less than the remaining desired duration,
100  * </ul>
101  * otherwise it will ask for an extension equal to the lease's remaining
102  * desired duration.
103  * <p>
104  * Once a lease is given to a lease renewal manager, the manager will
105  * continue to renew the lease until one of the following occurs:
106  * <ul>
107  * <li> The lease's desired or actual expiration time is reached.
108  * <li> An explicit removal of the lease from the set is requested via a
109  *	<code>cancel</code>, <code>clear</code>, or <code>remove</code>
110  *	call on the renewal manager.
111  * <li> The renewal manager tries to renew the lease and gets a bad
112  *	object exception, bad invocation exception, or
113  *	<code>LeaseException</code>.
114  * </ul>
115  * <p>
116  * The methods of this class are appropriately synchronized for
117  * concurrent operation. Additionally, this class makes certain
118  * guarantees with respect to concurrency. When this class makes a
119  * remote call (for example, when requesting the renewal of a lease),
120  * any invocations made on the methods of this class will not be
121  * blocked. Similarly, this class makes a reentrancy guarantee with
122  * respect to the listener objects registered with this class. Should
123  * this class invoke a method on a registered listener (a local call),
124  * calls from that method to any other method of this class are
125  * guaranteed not to result in a deadlock condition.
126  *
127  * @author Sun Microsystems, Inc.
128  * @see Lease
129  * @see LeaseException
130  * @see LeaseRenewalEvent 
131  *
132  *  <!-- Implementation Specifics -->
133  *
134  * The following implementation-specific items are discussed below:
135  * <ul>
136  * <li><a href="#configEntries">Configuring LeaseRenewalManager</a>
137  * <li><a href="#logging">Logging</a>
138  * <li><a href="#algorithm">The renewal algorithm</a>
139  * </ul>
140  *
141  * <a name="configEntries"><b>Configuring LeaseRenewalManager</b></a>
142  *
143  * This implementation of <code>LeaseRenewalManager</code> supports the
144  * following configuration entries, with component
145  * <code>net.jini.lease.LeaseRenewalManager</code>:
146  *
147  * <table summary="Describes the renewBatchTimeWindow configuration entry"
148  *	  border="0" cellpadding="2">
149  *   <tr valign="top">
150  *     <th scope="col">&#X2022;
151  *     <th scope="col" align="left" colspan="2"><code>
152  *	 renewBatchTimeWindow</code>
153  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
154  *     Type: <td> <code>long</code>
155  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
156  *     Default: <td> <code>5 * 60 * 1000 // 5 minutes</code>
157  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
158  *     Description: <td> The maximum number of milliseconds earlier than
159  *     a lease would typically be renewed to allow it to be renewed in
160  *     order to permit batching its renewal with that of other
161  *     leases. The value must not be negative. This entry is obtained
162  *     in the constructor.
163  * </table>
164  * <table summary="Describes the roundTripTime configuration entry"
165  *	  border="0" cellpadding="2">
166  *   <tr valign="top">
167  *     <th scope="col">&#X2022;
168  *     <th scope="col" align="left" colspan="2"><code>
169  *	 roundTripTime</code>
170  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
171  *     Type: <td> <code>long</code>
172  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
173  *     Default: <td> <code>10 * 1000 // 10 seconds</code>
174  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
175  *     Description: <td> The worst-case latency, expressed in milliseconds,
176  *     to assume for a remote call to renew a lease. The value must be greater 
177  *     than zero. Unrealistically low values for this entry may
178  *     result in failure to renew a lease. Leases managed by this manager
179  *     should have durations exceeding the <code>roundTripTime</code>.
180  *     This entry is obtained in the constructor.
181  * </table>
182  * <table summary="Describes the executorService configuration entry"
183  *	  border="0" cellpadding="2">
184  *   <tr valign="top">
185  *     <th scope="col">&#X2022;
186  *     <th scope="col" align="left" colspan="2"><code>
187  *	 executorService</code>
188  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
189  *     Type: <td> {@link ExecutorService}
190  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
191  *     Default: <td> <code>new ThreadPoolExecutor(1,11,15,TimeUnit.SECONDS,
192  *     new LinkedBlockingQueue())</code>
193  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
194  *     Description: <td> The object used to manage queuing tasks
195  *     involved with renewing leases and sending notifications. The
196  *     value must not be <code>null</code>. The default value creates
197  *     a maximum of 11 threads for performing operations, waits 15
198  *     seconds before removing idle threads.
199  * </table>
200  * <p>
201  * <a name="logging"><b>Logging</b></a>
202  * </p>
203  * <p>
204  * This implementation uses the {@link Logger} named
205  * <code>net.jini.lease.LeaseRenewalManager</code> to log information at
206  * the following logging levels: </p>
207  *
208  * <table border="1" cellpadding="5"
209  *	  summary="Describes logging performed by the
210  *		   LeaseRenewalManager at different logging levels">
211  *
212  * <caption><b><code>net.jini.lease.LeaseRenewalManager</code></b></caption>
213  *
214  * <tr> <th scope="col"> Level <th scope="col"> Description
215  *
216  * <tr> <td> {@link Levels#FAILED FAILED}
217  *	<td> Lease renewal failure events, or leases that expire before
218  *           reaching the desired expiration time
219  *
220  * <tr> <td> {@link Levels#HANDLED HANDLED}
221  *	<td> Lease renewal attempts that produce indefinite exceptions
222  *
223  * <tr> <td> {@link Level#FINE FINE}
224  *	<td> Adding and removing leases, lease renewal attempts, and desired
225  *	     lease expiration events
226  *
227  * </table> 
228  * <p>
229  *
230  * For a way of using the <code>FAILED</code> and <code>HANDLED</code> logging
231  * levels in standard logging configuration files, see the {@link LogManager}
232  * class.</p>
233  * <p>
234  * <a name="algorithm"><b>The renewal algorithm</b></a></p>
235  * <p>
236  * The time at which a lease is scheduled for renewal is based on the
237  * expiration time of the lease, possibly adjusted to account for the
238  * latency of the remote renewal call. The configuration entry
239  * <code>roundTripTime</code>, which defaults to ten seconds, represents
240  * the total time to make the remote call.</p>
241  * <p>
242  * The following pseudocode was derived from the code which computes
243  * the renewal time. In this code, <code>rtt</code> represents the
244  * value of the <code>roundTripTime</code>:</p>
245  *
246  * <pre>    
247  *          endTime = lease.getExpiration();
248  *          delta = endTime - now;
249  *          if (delta &lt;= rtt * 2) {
250  *	        delta = rtt;
251  *          } else if (delta &lt;= rtt * 8) {
252  *	        delta /= 2;
253  *          } else if (delta &lt;= 1000 * 60 * 60 * 24 * 7) {
254  *	        delta /= 8;
255  *          } else if (delta &lt;= 1000 * 60 * 60 * 24 * 14) {
256  *	        delta = 1000 * 60 * 60 * 24;
257  *          } else {
258  *	        delta = 1000 * 60 * 60 * 24 * 3;
259  *          }
260  *          renew = endTime - delta;
261  *</pre>
262  * <p>
263  * It is important to note that <code>delta</code> is never less than
264  * <code>rtt</code> when the renewal time is computed. A lease which 
265  * would expire within this time range will be scheduled for immediate
266  * renewal. The use of very short lease durations (at or below <code>rtt</code>)
267  * can cause the renewal manager to effectively ignore the lease duration
268  * and repeatedly schedule the lease for immediate renewal.
269  * </p><p>
270  * If an attempt to renew a lease fails with an indefinite exception, a
271  * renewal is rescheduled with an updated renewal time as computed by the
272  * following pseudocode:</p>
273  *
274  * <pre>
275  *          delta = endTime - renew;
276  *          if (delta &gt; rtt) {
277  *              if (delta &lt;= rtt * 3) {
278  *	            delta = rtt;
279  *              } else if (delta &lt;= 1000 * 60 * 60) {
280  *	            delta /= 3;
281  *              } else if (delta &lt;= 1000 * 60 * 60 * 24) {
282  *	            delta = 1000 * 60 * 30;
283  *              } else if (delta &lt;= 1000 * 60 * 60 * 24 * 7) {
284  *	            delta = 1000 * 60 * 60 * 3;
285  *              } else {
286  *	            delta = 1000 * 60 * 60 * 8;
287  *              }
288  *              renew += delta;
289  *          }
290  * </pre>
291  *
292  * Client leases are maintained in a collection sorted by descending renewal
293  * time. A renewal thread is spawned whenever the renewal time of the last lease
294  * in the collection is reached. This renewal thread examines all of the leases
295  * in the collection whose renewal time falls within
296  * <code>renewBatchTimeWindow</code> milliseconds of the renewal time of the
297  * last lease. If any of these leases can be batch renewed with the last lease (as
298  * determined by calling the {@link Lease#canBatch canBatch} method of
299  * the last lease) then a {@link LeaseMap} is created, all eligible leases
300  * are added to it and the {@link LeaseMap#renewAll} method is called. Otherwise, the
301  * last lease is renewed directly.
302  * <p> 
303  * The <code>ExecutorService</code> that manages the renewal threads has a bound on
304  * the number of simultaneous threads it will support. The renewal time of
305  * leases may be adjusted earlier in time to reduce the likelihood that the
306  * renewal of a lease will be delayed due to exhaustion of the thread pool.
307  * Actual renewal times are determined by starting with the lease with the
308  * latest (farthest off) desired renewal time and working backwards.  When
309  * computing the actual renewal time for a lease, the renewals of all leases
310  * with later renewal times, which will be initiated during the round trip time
311  * of the current lease's renewal, are considered.  If using the desired
312  * renewal time for the current lease would result in more in-progress renewals
313  * than the number of threads allowed, the renewal time of the current lease is
314  * shifted earlier in time, such that the maximum number of threads is not
315  * exceeded.
316  * 
317  */
318 
319 public class LeaseRenewalManager {
320 
321     private static final String LRM = "net.jini.lease.LeaseRenewalManager";
322 
323     private static final Logger logger = Logger.getLogger(LRM);
324 
325     /* Method objects for manipulating method constraints */
326     private static final Method cancelMethod;
327     private static final Method cancelAllMethod;
328     private static final Method renewMethod;
329     private static final Method renewAllMethod;
330     static {
331 	try {
332 	    cancelMethod = Lease.class.getMethod(
333 		"cancel", new Class[] { });
334 	    cancelAllMethod = LeaseMap.class.getMethod(
335 		"cancelAll", new Class[] { });
336 	    renewMethod = Lease.class.getMethod(
337 		"renew", new Class[] { long.class });
338 	    renewAllMethod = LeaseMap.class.getMethod(
339 		"renewAll", new Class[] { });
340 	} catch (NoSuchMethodException e) {
341 	    throw new NoSuchMethodError(e.getMessage());
342 	}
343     }
344 
345     /* Methods for comparing lease constraints. */
346     private static final Method[] leaseToLeaseMethods = {
347 	cancelMethod, cancelMethod, renewMethod, renewMethod
348     };
349 
350     /* Methods for converting lease constraints to lease map constraints. */
351     private static final Method[] leaseToLeaseMapMethods = {
352 	cancelMethod, cancelAllMethod, renewMethod, renewAllMethod
353     };
354 
355     private final long renewBatchTimeWindow;
356 
357     /** Task manager for queuing and renewing leases 
358      *  NOTE: test failures occur with queue's that have capacity, 
359      *  no test failures occur with SynchronousQueue, for the time
360      *  being, until the cause is sorted out we may need to rely on 
361      *  a larger pool, if necessary.  TaskManager is likely to have 
362      *  lower throughput capacity that ExecutorService with a
363      *  SynchronousQueue although this hasn't been confirmed yet.
364      */
365     final ExecutorService leaseRenewalExecutor;
366 
367     /**
368      * The worst-case renewal round-trip-time
369      */
370     private final long renewalRTT ;
371 
372     /** 
373      * Entries for leases that are not actively being renewed.
374      * Lease with the earliest renewal is last in the map.
375      */
376     private final SortedMap leases = new TreeMap();
377 
378     /** Entries for leases that are actively being renewed */
379     private final List leaseInRenew = new ArrayList(1);
380     /** The queuer task */
381     private QueuerTask queuer = null;
382 
383     /**
384      * Used to determine concurrency constraints when calculating actual
385      * renewals.  The list is stored in a field to avoid reallocating it.
386      */
387     private List calcList;
388 
389     private final class RenewTask implements Runnable {
390 	/** Entries of leases to renew (if multiple, all can be batched) */
391 	private final List bList;
392 
393 	/** 
394 	 * True if this task only holds leases that have reached their
395 	 * actual or desired expiration
396 	 */
397 	private final boolean noRenewals;
398 
399 	/**
400 	 * Create a collection of entries whose leases can be batch
401 	 * renewed with the last lease in the map, or a list of entries
402 	 * whose leases need to be removed.  Which is created depends on
403 	 * the state of the last lease in the map.  Remove each entry
404 	 * from the map, and add them to leaseInRenew.
405 	 */
406 	RenewTask(long now) {
407 	    bList = new ArrayList(1);
408 	    Entry e = (Entry) leases.lastKey();
409 
410 	    if (e.renewalsDone() || e.endTime <= now) {
411 		noRenewals = true;
412 		Map lMap = leases.tailMap(new Entry(now, renewalRTT));
413 		for (Iterator iter = lMap.values().iterator(); 
414 		     iter.hasNext(); )
415 		{
416 		    Entry be = (Entry) iter.next();
417 		    if (be.renewalsDone() || be.endTime <= now) {
418 			iter.remove();
419 			logExpiration(be);
420 			/*
421 			 * Only add to bList if we need to tell someone
422 			 * about this lease's departure
423 			 */
424 			if (be.listener != null)
425 			    bList.add(be);
426 		    }
427 		}
428 	    } else {
429 		noRenewals = false;
430 		Map lMap = leases.tailMap(new Entry(e.renew + renewBatchTimeWindow, renewalRTT));
431 		for (Iterator iter = lMap.values().iterator(); 
432 		     iter.hasNext(); )
433 		{
434 		    Entry be = (Entry) iter.next();
435 		    if (be == e || be.canBatch(e)) {
436 			iter.remove();
437 			leaseInRenew.add(be);
438 			bList.add(be);
439 		    }
440 		}
441 	    }
442 	}
443 
444         @Override
445 	public void run() {
446 	    if (noRenewals) {
447 		// Just notify
448 		tell(bList);
449 	    } else {
450 		/*
451 		 * Get rid of any leases that have expired and then do
452 		 * renewals
453 		 */
454 		long now = System.currentTimeMillis();
455 		List bad = processBadLeases(now);
456 		if (!bList.isEmpty())
457 		    renewAll(bList, now);
458 		if (bad != null)
459 		    tell(bad);
460 	    }
461 	}
462 
463 	/**
464 	 * Find any expired leases, remove them from bList and
465 	 * leaseInRenew, and return any with listeners.
466 	 */
467 	private List processBadLeases(long now) {
468 	    List bad = null;
469 	    synchronized (LeaseRenewalManager.this) {
470 		for (Iterator iter = bList.iterator(); iter.hasNext(); ) {
471 		    Entry e = (Entry) iter.next();
472 		    if (e.endTime <= now) {
473 			iter.remove();
474 			logExpiration(e);
475 			removeLeaseInRenew(e);
476 			if (e.listener != null) {
477 			    if (bad == null)
478 				bad = new ArrayList(1);
479 			    bad.add(e);
480 			}
481 		    }
482 		}
483 	    }
484 	    return bad;
485 	}
486     }
487 
488     private static class Entry implements Comparable {
489 	/*
490 	 * Since the cnt only gets modified in the constructor, and the
491 	 * constructor is always called from synchronized code, the cnt
492 	 * does not need to be synchronized.
493 	 */
494 	private static long cnt = 0;
495 
496 	/** Unique id */
497 	public final long id;
498 	/** The lease */
499 	public final Lease lease;
500 	/** Desired expiration */
501 	public long expiration;
502 	/** Renew duration */
503 	public long renewDuration;
504 	/** The listener, or null */
505 	public final LeaseListener listener;
506 	/** Current actual expiration */
507 	public long endTime;
508         private final long renewalRTT;
509 
510 	/** 
511 	 * The next time we have to do something with this lease.
512 	 * Usually a renewal, but could be removing it from the managed
513 	 * set because its desired expiration has been reached.
514 	 */
515 	public long renew;
516 
517 	/** Actual time to renew, given concurrency limitations */
518 	public long actualRenew;
519 	/** Renewal exception, or null */
520 	public Throwable ex = null;
521 
522 	public Entry(Lease lease, long expiration, long renewDuration, long renewalRTT, LeaseListener listener)
523 	{
524 	    this.endTime = lease.getExpiration();
525 	    this.lease = lease;
526 	    this.expiration = expiration;
527 	    this.renewDuration = renewDuration;
528 	    this.listener = listener;
529             this.renewalRTT = renewalRTT;
530 	    id = cnt++;
531 	}
532 
533 	/** Create a fake entry for tailMap */
534 	public Entry(long renew, long renewalRTT) {
535 	    this.renew = renew;
536 	    id = Long.MAX_VALUE;
537 	    lease = null;
538 	    listener = null;
539             this.renewalRTT = renewalRTT;
540 	}
541 
542 	/**
543 	 * If the renewDuration is ANY, return ANY, otherwise return the
544 	 * minimum of the renewDuration and the time remaining until the
545 	 * desired expiration.
546 	 */
547 	public long getRenewDuration(long now) {
548 	    if (renewDuration == Lease.ANY)
549 		return renewDuration;
550 	    return Math.min(expiration - now, renewDuration);
551 	}
552 
553 	/** Calculate the renew time for the lease entry */
554 	public void calcRenew(long now) {
555 	    endTime = lease.getExpiration();
556 	    if (renewalsDone()) {
557 		if (null == desiredExpirationListener()) {
558 		    // Nothing urgent needs to be done with this lease
559 		    renew = Long.MAX_VALUE;
560 		} else {
561 		    /*
562 		     * Tell listener about dropping this lease in a
563 		     * timely fashion
564 		     */
565 		    renew = expiration; 
566 		}
567 		return;
568 	    }
569 	    long delta = endTime - now;
570 	    if (delta <= renewalRTT * 2) {
571 		delta = renewalRTT;
572 	    } else if (delta <= renewalRTT * 8) {
573 		delta /= 2;
574 	    } else if (delta <= 1000 * 60 * 60 * 24 * 7) {
575 		delta /= 8;
576 	    } else if (delta <= 1000 * 60 * 60 * 24 * 14) {
577 		delta = 1000 * 60 * 60 * 24;
578 	    } else {
579 		delta = 1000 * 60 * 60 * 24 * 3;
580 	    }
581 	    renew = endTime - delta;
582 	}
583 
584 	/** Calculate a new renew time due to an indefinite exception */
585 	public void delayRenew() {
586 	    long delta = endTime - renew;
587 	    if (delta <= renewalRTT) {
588 		return;
589 	    } else if (delta <= renewalRTT * 3) {
590 		 delta = renewalRTT;
591 	    } else if (delta <= 1000 * 60 * 60) {
592 		delta /= 3;
593 	    } else if (delta <= 1000 * 60 * 60 * 24) {
594 		delta = 1000 * 60 * 30;
595 	    } else if (delta <= 1000 * 60 * 60 * 24 * 7) {
596 		delta = 1000 * 60 * 60 * 3;
597 	    } else {
598 		delta = 1000 * 60 * 60 * 8;
599 	    }
600 	    renew += delta;
601 	}
602 
603 	/** Sort by decreasing renew time, secondary sort by decreasing id */
604         @Override
605 	public int compareTo(Object obj) {
606 	    if (this == obj)
607 		return 0;
608 	    Entry e = (Entry) obj;
609 	    if (renew < e.renew || (renew == e.renew && id < e.id))
610 		return 1;
611 	    return -1;
612 	}
613 	
614 	@Override
615 	public boolean equals(Object o){
616 	    if (!(o instanceof Entry)) return false;
617 	    Entry that = (Entry) o;
618 	    if (this.id != that.id) return false;
619 	    if (this.renew != that.renew) return false;
620 	    if (this.renewalRTT != that.renewalRTT) return false;
621 	    if (!Objects.equals(this.lease, that.lease)) return false;
622 	    return Objects.equals(this.listener, that.listener);
623 	}
624 
625 	@Override
626 	public int hashCode() {
627 	    int hash = 7;
628 	    hash = 67 * hash + (int) (this.id ^ (this.id >>> 32));
629 	    hash = 67 * hash + (this.lease != null ? this.lease.hashCode() : 0);
630 	    hash = 67 * hash + (this.listener != null ? this.listener.hashCode() : 0);
631 	    hash = 67 * hash + (int) (this.renewalRTT ^ (this.renewalRTT >>> 32));
632 	    hash = 67 * hash + (int) (this.renew ^ (this.renew >>> 32));
633 	    return hash;
634 	}
635 
636 	/**
637 	 * Returns true if the renewal of this lease can be batched with
638 	 * the (earlier) renewal of the given lease.  This method must
639 	 * be called with an entry such that e.renew &lt;= this.renew. <p>
640 	 * 
641 	 * First checks that both leases require renewal, have the same
642 	 * client constraints, and can be batched.  Then enforces
643 	 * additional requirements to avoid renewing the lease too much
644 	 * more often than necessary. <p>
645 	 *
646 	 * One of the following must be true: <ul>
647 	 *
648 	 * <li> This lease has a renewal duration of Lease.ANY, meaning
649 	 * it doesn't specify its renewal duration.
650 	 *
651 	 * <li> The amount of time from the other lease's renewal time
652 	 * to this one's is less than half of the estimated time needed
653 	 * to perform renewals (renewalRTT).  In this case, the renewal
654 	 * times are so close together that the renewal duration
655 	 * shouldn't be materially affected.
656 	 *
657 	 * <li> This lease's expiration time is no more than half its
658 	 * renewal duration greater than the renewal time of the other
659 	 * lease.  This case insures that this lease is not renewed
660 	 * until at least half of it's renewal duration has
661 	 * elapsed. </ul> <p>
662 	 *
663 	 * In addition, one of the following must be true: <ul>
664 	 *
665 	 * <li> The other lease has a renewal duration of Lease.ANY,
666 	 * meaning we don't know how long its next renewal will be.
667 	 *
668 	 * <li> The other lease is not going to be renewed again before
669 	 * this lease's renewal time, because either its next renewal
670 	 * will last until after this lease's renewal time or it will
671 	 * only be renewed once more. </ul>
672 	 */
673 	public boolean canBatch(Entry e) {
674 	    return (!renewalsDone() &&
675 		    !e.renewalsDone() &&
676 		    sameConstraints(lease, e.lease) &&
677 		    lease.canBatch(e.lease) &&
678 		    (renewDuration == Lease.ANY ||
679 		     renew - e.renew <= renewalRTT / 2 ||
680 		     endTime - e.renew <= renewDuration / 2) &&
681 		    (e.renewDuration == Lease.ANY ||
682 		     e.renew > renew - e.renewDuration ||
683 		     e.renew >= e.expiration - e.renewDuration));
684 	}
685 
686 	/**
687 	 * Returns true if the two leases both implement RemoteMethodControl
688 	 * and have the same constraints for Lease methods, or both don't
689 	 * implement RemoteMethodControl, else returns false.
690 	 */
691 	private static boolean sameConstraints(Lease l1, Lease l2) {
692 	    if (!(l1 instanceof RemoteMethodControl)) {
693 		return !(l2 instanceof RemoteMethodControl);
694 	    } else if (!(l2 instanceof RemoteMethodControl)) {
695 		return false;
696 	    } else {
697 		return ConstrainableProxyUtil.equivalentConstraints(
698 		    ((RemoteMethodControl) l1).getConstraints(),
699 		    ((RemoteMethodControl) l2).getConstraints(),
700 		    leaseToLeaseMethods);
701 	    }
702 	}
703 
704 	/**
705 	 * Return the DesiredExpirationListener associated with this
706 	 * lease, or null if there is none.
707 	 */
708 	public DesiredExpirationListener desiredExpirationListener() {
709 	    if (listener == null)
710 		return null;
711 
712 	    if (listener instanceof DesiredExpirationListener) 
713 		return (DesiredExpirationListener) listener;
714 
715 	    return null;
716 	}
717 
718 	/**
719 	 * Return true if the actual expiration is greater than or equal
720 	 * to the desired expiration (e.g. we don't need to renew this
721 	 * lease any more.
722 	 */
723 	public boolean renewalsDone() {
724 	    return expiration <= endTime;
725 	}
726     }
727 
728     /**
729      * No-argument constructor that creates an instance of this class
730      * that initially manages no leases.
731      */
732     public LeaseRenewalManager() {
733         this.renewBatchTimeWindow = 1000 * 60 * 5;
734         this.renewalRTT = 10 * 1000;
735         leaseRenewalExecutor = 
736             new ThreadPoolExecutor(
737                     1,  /* min threads */
738                     11, /* max threads */
739                     15,
740                     TimeUnit.SECONDS, 
741                     new SynchronousQueue<Runnable>(), /* Queue has no capacity */
742                     new NamedThreadFactory("LeaseRenewalManager",false),
743                     new CallerRunsPolicy()
744             );
745     }
746     
747     private static Init init(Configuration config) throws ConfigurationException{
748         return new Init(config);
749     }
750     
751     private static class Init {
752         long renewBatchTimeWindow = 1000 * 60 * 5 ;
753         long renewalRTT = 10 * 1000;
754         ExecutorService leaseRenewalExecutor;
755         
756         Init(Configuration config) throws ConfigurationException{
757             if (config == null) {
758 	    throw new NullPointerException("config is null");
759             }
760             renewBatchTimeWindow = Config.getLongEntry(
761                 config, LRM, "renewBatchTimeWindow",
762                 renewBatchTimeWindow, 0, Long.MAX_VALUE);
763             renewalRTT = Config.getLongEntry(
764                 config, LRM, "roundTripTime",
765                 renewalRTT, 1, Long.MAX_VALUE);
766             leaseRenewalExecutor = Config.getNonNullEntry(
767                 config, 
768                 LRM, 
769                 "executorService", 
770                 ExecutorService.class,
771                 new ThreadPoolExecutor(
772                         1,  /* Min Threads */
773                         11, /* Max Threads */
774                         15,
775                         TimeUnit.SECONDS, 
776                         new SynchronousQueue<Runnable>(), /* No capacity */
777                         new NamedThreadFactory("LeaseRenewalManager",false),
778                         new CallerRunsPolicy()
779                 ) 
780             );
781         }
782     }
783 
784     /**
785      * Constructs an instance of this class that initially manages no leases
786      * and that uses <code>config</code> to control implementation-specific
787      * details of the behavior of the instance created.
788      *
789      * @param config supplies entries that control the configuration of this
790      *	      instance
791      * @throws ConfigurationException if a problem occurs when obtaining
792      *	       entries from the configuration
793      * @throws NullPointerException if the configuration is <code>null</code>
794      */
795     public LeaseRenewalManager(Configuration config)
796 	throws ConfigurationException
797     {
798 	this(init(config));
799     }
800     
801     private LeaseRenewalManager(Init init){
802         this.renewBatchTimeWindow = init.renewBatchTimeWindow;
803         this.renewalRTT = init.renewalRTT;
804         this.leaseRenewalExecutor = init.leaseRenewalExecutor;
805     }
806 
807     /**
808      * Constructs an instance of this class that will initially manage a
809      * single lease. Employing this form of the constructor is
810      * equivalent to invoking the no-argument form of the constructor
811      * followed by an invocation of the three-argument form of the
812      * <code>renewUntil</code> method. See <code>renewUntil</code> for
813      * details on the arguments and what exceptions may be thrown by
814      * this constructor.
815      *
816      * @param lease reference to the initial lease to manage
817      * @param desiredExpiration the desired expiration for
818      *	      <code>lease</code>
819      * @param listener reference to the <code>LeaseListener</code>
820      *	      object that will receive notifications of any exceptional
821      *	      conditions that occur during renewal attempts. If
822      *	      <code>null</code> no notifications will be sent.
823      * @throws NullPointerException if <code>lease</code> is
824      *	       <code>null</code>
825      * @see LeaseListener
826      * @see #renewUntil 
827      */
828     public LeaseRenewalManager(Lease lease,
829 			       long desiredExpiration,
830 			       LeaseListener listener)
831     {
832         this.renewBatchTimeWindow = 1000 * 60 * 5;
833         this.renewalRTT = 10 * 1000;
834         leaseRenewalExecutor = new ThreadPoolExecutor(
835                 1,  /* Min Threads */
836                 11, /* Max Threads */
837                 15,
838                 TimeUnit.SECONDS, 
839                 new SynchronousQueue<Runnable>(), /* No Capacity */
840                 new NamedThreadFactory("LeaseRenewalManager",false),
841                 new CallerRunsPolicy()
842         );
843 	renewUntil(lease, desiredExpiration, listener);
844     }
845 
846     /**
847      * Include a lease in the managed set until a specified time. 
848      * <p>
849      * If <code>desiredExpiration</code> is <code>Lease.ANY</code>
850      * calling this method is equivalent the following call:
851      * <pre>
852      *     renewUntil(lease, Lease.FOREVER, Lease.ANY, listener)
853      * </pre>
854      * otherwise it is equivalent to this call:
855      * <pre>
856      *     renewUntil(lease, desiredExpiration, Lease.FOREVER, listener)
857      * </pre>
858      * <p>
859      * @param lease the <code>Lease</code> to be managed
860      * @param desiredExpiration when the client wants the lease to
861      *	      expire, in milliseconds since the beginning of the epoch
862      * @param listener reference to the <code>LeaseListener</code>
863      *	      object that will receive notifications of any exceptional
864      *	      conditions that occur during renewal attempts. If
865      *	      <code>null</code> no notifications will be sent.
866      * @throws NullPointerException if <code>lease</code> is
867      *	       <code>null</code>
868      * @see #renewUntil
869      */
870     public final void renewUntil(Lease lease,
871 			   long desiredExpiration,
872 			   LeaseListener listener)
873     {
874 	if (desiredExpiration == Lease.ANY) {
875 	    renewUntil(lease, Lease.FOREVER, Lease.ANY, listener);
876 	} else {
877 	    renewUntil(lease, desiredExpiration, Lease.FOREVER, listener);
878 	}
879     }
880 
881     /**
882      * Include a lease in the managed set until a specified time and
883      * with a specified renewal duration.
884      * <p>
885      * This method takes as arguments: a reference to the lease to
886      * manage, the desired expiration time of the lease, the renewal
887      * duration time for the lease, and a reference to the
888      * <code>LeaseListener</code> object that will receive notification
889      * of exceptional conditions when attempting to renew this
890      * lease. The <code>LeaseListener</code> argument may be
891      * <code>null</code>.
892      * <p>
893      * If the <code>lease</code> argument is <code>null</code>, a
894      * <code>NullPointerException</code> will be thrown. If the
895      * <code>desiredExpiration</code> argument is
896      * <code>Lease.FOREVER</code>, the <code>renewDuration</code>
897      * argument may be <code>Lease.ANY</code> or any positive value;
898      * otherwise, the <code>renewDuration</code> argument must be a
899      * positive value. If the <code>renewDuration</code> argument does
900      * not meet these requirements, an
901      * <code>IllegalArgumentException</code> will be thrown.
902      * <p>
903      * If the lease passed to this method is already in the set of
904      * managed leases, the listener object, the desired expiration, and
905      * the renewal duration associated with that lease will be replaced
906      * with the new listener, desired expiration, and renewal duration.
907      * <p>
908      * The lease will remain in the set until one of the following
909      * occurs:
910      * <ul>
911      * <li> The lease's desired or actual expiration time is reached.
912      * <li> An explicit removal of the lease from the set is requested
913      *	    via a <code>cancel</code>, <code>clear</code>, or
914      *	    <code>remove</code> call on the renewal manager.
915      * <li> The renewal manager tries to renew the lease and gets a bad
916      *	    object exception, bad invocation exception, or
917      *	    <code>LeaseException</code>.
918      * </ul>
919      * <p>
920      * This method will interpret the value of the
921      * <code>desiredExpiration</code> argument as the desired absolute
922      * system time after which the lease is no longer valid. This
923      * argument provides the ability to indicate an expiration time that
924      * extends beyond the actual expiration of the lease. If the value
925      * passed for this argument does indeed extend beyond the lease's
926      * actual expiration time, then the lease will be systematically
927      * renewed at appropriate times until one of the conditions listed
928      * above occurs. If the value is less than or equal to the actual
929      * expiration time, nothing will be done to modify the time when the
930      * lease actually expires. That is, the lease will not be renewed
931      * with an expiration time that is less than the actual expiration
932      * time of the lease at the time of the call.
933      * <p>
934      * If the <code>LeaseListener</code> argument is a
935      * non-<code>null</code> object reference, it will receive
936      * notification of exceptional conditions occurring upon a renewal
937      * attempt of the lease. In particular, exceptional conditions
938      * include the reception of a <code>LeaseException</code>, bad
939      * object exception, or bad invocation exception (collectively these
940      * are referred to as <em>definite exceptions</em>) during a renewal
941      * attempt or the lease's actual expiration being reached before its
942      * desired expiration.
943      * <p>
944      * If a definite exception occurs during a lease renewal request,
945      * the exception will be wrapped in an instance of the
946      * <code>LeaseRenewalEvent</code> class and sent to the listener.
947      * <p>
948      * If an indefinite exception occurs during a renewal request for
949      * the lease, renewal requests will continue to be made for that
950      * lease until: the lease is renewed successfully, a renewal attempt
951      * results in a definite exception, or the lease's actual expiration
952      * time has been exceeded. If the lease cannot be successfully
953      * renewed before its actual expiration is reached, the exception
954      * associated with the most recent renewal attempt will be wrapped
955      * in an instance of the <code>LeaseRenewalEvent</code> class and
956      * sent to the listener.
957      * <p>
958      * If the lease's actual expiration is reached before the lease's
959      * desired expiration time, and either 1) the last renewal attempt
960      * succeeded or 2) there have been no renewal attempts, a
961      * <code>LeaseRenewalEvent</code> containing a <code>null</code>
962      * exception will be sent to the listener.
963      *
964      * @param lease the <code>Lease</code> to be managed
965      * @param desiredExpiration when the client wants the lease to
966      *	      expire, in milliseconds since the beginning of the epoch
967      * @param renewDuration the renewal duration to associate with the
968      *	      lease, in milliseconds
969      * @param listener reference to the <code>LeaseListener</code>
970      *	      object that will receive notifications of any exceptional
971      *	      conditions that occur during renewal attempts. If
972      *	      <code>null</code>, no notifications will be sent.
973      * @throws NullPointerException if <code>lease</code> is
974      *	       <code>null</code>
975      * @throws IllegalArgumentException if <code>renewDuration</code> is
976      *	       invalid
977      * @see LeaseRenewalEvent
978      * @see LeaseException
979      */
980     public void renewUntil(Lease lease,
981 			   long desiredExpiration,
982 			   long renewDuration,
983 			   LeaseListener listener)
984     {
985 	validateDuration(renewDuration, desiredExpiration == Lease.FOREVER,
986 			 "desiredExpiration");
987 	addLease(lease, desiredExpiration, renewDuration, listener,
988 		 System.currentTimeMillis());
989     }
990 
991     /**
992      * Include a lease in the managed set for a specified duration.
993      * <p>
994      * Calling this method is equivalent the following call:
995      * <pre>
996      *     renewFor(lease, desiredDuration, Lease.FOREVER, listener)
997      * </pre>
998      *
999      * @param lease reference to the new lease to manage
1000      * @param desiredDuration the desired duration (relative time) that
1001      *	      the caller wants <code>lease</code> to be valid for, in
1002      *	      milliseconds
1003      * @param listener reference to the <code>LeaseListener</code>
1004      *	      object that will receive notifications of any exceptional
1005      *	      conditions that occur during renewal attempts. If
1006      *	      <code>null</code>, no notifications will be sent.
1007      * @throws NullPointerException if <code>lease</code> is
1008      *	       <code>null</code>
1009      * @see #renewFor 
1010      */
1011     public void renewFor(Lease lease, long desiredDuration, 
1012 			 LeaseListener listener) 
1013     {
1014 	renewFor(lease, desiredDuration, Lease.FOREVER, listener);
1015     }	 
1016 
1017     /**
1018      * Include a lease in the managed set for a specified duration and
1019      * with specified renewal duration.
1020      * <p>
1021      * The semantics of this method are similar to those of the
1022      * four-argument form of <code>renewUntil</code>, with
1023      * <code>desiredDuration</code> + current time being used for the
1024      * value of the <code>desiredExpiration</code> argument of
1025      * <code>renewUntil</code>. The only exception to this is that, in
1026      * the context of <code>renewFor</code>, the value of the
1027      * <code>renewDuration</code> argument may only be
1028      * <code>Lease.ANY</code> if the value of the
1029      * <code>desiredDuration</code> argument is <em>exactly</em>
1030      * <code>Lease.FOREVER.</code>
1031      * <p>
1032      * This method tests for arithmetic overflow in the desired
1033      * expiration time computed from the value of
1034      * <code>desiredDuration</code> argument
1035      * (<code>desiredDuration</code> + current time). Should such
1036      * overflow be present, a value of <code>Lease.FOREVER</code> is
1037      * used to represent the lease's desired expiration time.
1038      *
1039      * @param lease reference to the new lease to manage
1040      * @param desiredDuration the desired duration (relative time) that
1041      *	      the caller wants <code>lease</code> to be valid for, in
1042      *	      milliseconds
1043      * @param renewDuration the renewal duration to associate with the
1044      *	      lease, in milliseconds
1045      * @param listener reference to the <code>LeaseListener</code>
1046      *	      object that will receive notifications of any exceptional
1047      *	      conditions that occur during renewal attempts. If
1048      *	      <code>null</code>, no notifications will be sent.
1049      * @throws NullPointerException if <code>lease</code> is
1050      *	       <code>null</code>
1051      * @throws IllegalArgumentException if <code>renewDuration</code> is
1052      *	       invalid
1053      * @see #renewUntil 
1054      */
1055     public void renewFor(Lease lease,
1056 			 long desiredDuration,
1057 			 long renewDuration,
1058 			 LeaseListener listener)
1059     {
1060 	/*
1061 	 * Validate before calculating effective desiredExpiration, if
1062 	 * they want a renewDuration of Lease.ANY, desiredDuration has
1063 	 * to be exactly Lease.FOREVER
1064 	 */
1065 	validateDuration(renewDuration, desiredDuration == Lease.FOREVER,
1066 			 "desiredDuration");
1067 
1068 	long now = System.currentTimeMillis();
1069 	long desiredExpiration;
1070 	if (desiredDuration < Lease.FOREVER - now) { // check overflow.
1071 	    desiredExpiration = now + desiredDuration;
1072 	} else {
1073 	    desiredExpiration = Lease.FOREVER;
1074 	}
1075 	addLease(lease, desiredExpiration, renewDuration, listener, now);
1076     }
1077 
1078     /**
1079      * Error checking function that ensures renewDuration is valid taking
1080      * into account the whether or not the desired expiration/duration is
1081      * Lease.FOREVER. Throws an appropriate IllegalArgumentException if
1082      * an invalid renewDuration is passed.
1083      *
1084      * @param renewDuration renew duration the clients wants
1085      * @param isForever should be true if client asked for a desired
1086      *	      expiration/duration of exactly Lease.FOREVER
1087      * @param name name of the desired expiration/duration field, used
1088      *	      to construct exception
1089      * @throws IllegalArgumentException if renewDuration is invalid
1090      */
1091     private void validateDuration(long renewDuration, boolean isForever, 
1092 				  String name) 
1093     {
1094 	if (renewDuration <= 0 && 
1095 	    !(renewDuration == Lease.ANY && isForever))
1096 	{
1097 	    /*
1098 	     * A negative renew duration and is not lease.ANY with a
1099 	     * forever desired expiration
1100 	     */
1101 	    if (renewDuration == Lease.ANY) {
1102 		/*
1103 		 * Must have been Lease.ANY with a non-FOREVER desired
1104 		 * expiration
1105 		 */
1106 		throw new IllegalArgumentException("A renewDuration of " +
1107 		     "Lease.ANY can only be used with a " + name + " of " +
1108 		     "Lease.FOREVER");
1109 	    } 
1110 
1111 	    if (isForever) {
1112 		// Must have been a non-Lease.ANY, non-positive renewDuration
1113 		throw new IllegalArgumentException("When " + name + " is " +
1114 		    "Lease.FOREVER the only valid values for renewDuration " +
1115 		    "are a positive number, Lease.ANY, or Lease.FOREVER");
1116 	    }
1117 
1118 	    /*
1119 	     * Must be a non-positive renewDuration with a non-Forever 
1120 	     * desired expiration
1121 	     */
1122 	    throw new IllegalArgumentException("When the " + name +
1123 		" is not Lease.FOREVER the only valid values for " +
1124 		"renewDuration are a positive number or Lease.FOREVER");
1125 	}
1126     }
1127 
1128     private synchronized void addLease(Lease lease,
1129 				       long desiredExpiration,
1130 				       long renewDuration,
1131 				       LeaseListener listener,
1132 				       long now)
1133     {	    
1134 	Entry e = findEntryDo(lease);
1135 	if (e != null && !removeLeaseInRenew(e))
1136 	    leases.remove(e);
1137 	insertEntry(new Entry(lease, desiredExpiration, renewDuration, renewalRTT,
1138 			      listener),
1139 		    now);
1140 	calcActualRenews(now);
1141 	logger.log(Level.FINE, "Added lease {0}", lease);
1142     }
1143 
1144     /** Calculate the preferred renew time, and put in the map */
1145     private void insertEntry(Entry e, long now) {
1146 	e.calcRenew(now);
1147 	leases.put(e, e);
1148     }
1149 
1150     /**
1151      * Returns the current desired expiration time associated with a
1152      * particular lease, (not the actual expiration that was granted
1153      * when the lease was created or last renewed).
1154      *
1155      * @param lease the lease the caller wants the current desired
1156      *	      expiration for
1157      * @return a <code>long</code> value corresponding to the current
1158      *	       desired expiration time associated with <code>lease</code>
1159      * @throws UnknownLeaseException if the lease passed to this method
1160      *	       is not in the set of managed leases
1161      * @see UnknownLeaseException
1162      * @see #setExpiration 
1163      */
1164     public synchronized long getExpiration(Lease lease)
1165 	throws UnknownLeaseException
1166     {
1167 	return findEntry(lease).expiration;
1168     }
1169 
1170     /**
1171      * Replaces the current desired expiration of a given lease from the
1172      * managed set with a new desired expiration time.
1173      * <p>
1174      * Note that an invocation of this method with a lease that is
1175      * currently a member of the managed set is equivalent to an
1176      * invocation of the <code>renewUntil</code> method with the lease's
1177      * current listener as that method's <code>listener</code>
1178      * argument. Specifically, if the value of the
1179      * <code>expiration</code> argument is less than or equal to the
1180      * lease's current desired expiration, this method takes no action.
1181      *
1182      * @param lease the lease whose desired expiration time should be
1183      *	      replaced
1184      * @param expiration <code>long</code> value representing the new
1185      *	      desired expiration time for the <code>lease</code>
1186      *	      argument
1187      * @throws UnknownLeaseException if the lease passed to this method
1188      *	       is not in the set of managed leases
1189      * @see #renewUntil
1190      * @see UnknownLeaseException
1191      * @see #getExpiration 
1192      */
1193     public synchronized void setExpiration(Lease lease, long expiration)
1194 	throws UnknownLeaseException
1195     {
1196 	Entry e = findEntry(lease);
1197 	e.expiration = expiration;
1198 	if (expiration != Lease.FOREVER && e.renewDuration == Lease.ANY)
1199 	    e.renewDuration = Lease.FOREVER;
1200 	if (leaseInRenew.indexOf(e) < 0) {
1201 	    leases.remove(e);
1202 	    long now = System.currentTimeMillis();
1203 	    insertEntry(e, now);
1204 	    calcActualRenews(now);
1205 	}
1206     }
1207 
1208     /**
1209      * Removes a given lease from the managed set, and cancels it.
1210      * <p>
1211      * Note that even if an exception is thrown as a result of the
1212      * cancel operation, the lease will still have been removed from the
1213      * set of leases managed by this class. Additionally, any exception
1214      * thrown by the <code>cancel</code> method of the lease object
1215      * itself may also be thrown by this method.
1216      *
1217      * @param lease the lease to remove and cancel
1218      * @throws UnknownLeaseException if the lease passed to this method
1219      *	       is not in the set of managed leases
1220      * @throws RemoteException typically, this exception occurs when
1221      *         there is a communication failure between the client and
1222      *         the server. When this exception does occur, the lease may
1223      *         or may not have been successfully cancelled, (but the
1224      *         lease is guaranteed to have been removed from the managed
1225      *         set).
1226      * @see Lease#cancel
1227      * @see UnknownLeaseException
1228      */
1229     public void cancel(Lease lease)
1230 	throws UnknownLeaseException, RemoteException
1231     {
1232 	remove(lease);
1233 	lease.cancel();
1234     }
1235     
1236     public void close(){
1237         leaseRenewalExecutor.shutdown();
1238     }
1239 
1240     /**
1241      * Removes a given lease from the managed set of leases; but does
1242      * not cancel the given lease.
1243      *
1244      * @param lease the lease to remove from the managed set
1245      * @throws UnknownLeaseException if the lease passed to this method
1246      *         is not in the set of managed leases
1247      * @see UnknownLeaseException
1248      */
1249     public synchronized void remove(Lease lease) throws UnknownLeaseException {
1250 	Entry e = findEntry(lease);
1251 	if (!removeLeaseInRenew(e))
1252 	    leases.remove(e);
1253 	calcActualRenews();
1254 	logger.log(Level.FINE, "Removed lease {0}", lease);
1255     }
1256 
1257     /**
1258      * Removes all leases from the managed set of leases. This method
1259      * does not request the cancellation of the removed leases.
1260      */
1261     public synchronized void clear() {
1262 	leases.clear();
1263 	leaseInRenew.clear();
1264 	calcActualRenews();
1265 	logger.log(Level.FINE, "Removed all leases");
1266     }
1267 
1268     /** Calculate the actual renew times, and poke/restart the queuer */
1269     private void calcActualRenews() {
1270 	calcActualRenews(System.currentTimeMillis());
1271     }
1272 
1273     /** Calculate the actual renew times, and poke/restart the queuer */
1274     private void calcActualRenews(long now) {
1275 	/*
1276 	 * Subtract one to account for the queuer thread, which should not be
1277 	 * counted.
1278 	 */
1279 	int maxThreads = leaseRenewalExecutor instanceof ThreadPoolExecutor ? 
1280             ((ThreadPoolExecutor)leaseRenewalExecutor).getMaximumPoolSize() - 1 
1281                 : 10;
1282 	if (calcList == null) {
1283 	    calcList = new ArrayList(maxThreads);
1284 	}
1285 	for (Iterator iter = leases.values().iterator(); iter.hasNext(); ) {
1286 	    Entry e = (Entry) iter.next();
1287 
1288 	    // Start by assuming we can renew the lease when we want
1289 	    e.actualRenew = e.renew;
1290 
1291 	    if (e.renewalsDone()) {
1292 		/*
1293 		 * The lease's actual expiration is >= desired
1294 		 * expiration, drop the lease if the desired expiration
1295 		 * has been reached and we don't have to tell anyone
1296 		 * about it
1297 		 */
1298 		if (now >= e.expiration && 
1299 		    e.desiredExpirationListener() == null) 
1300 		{
1301 		    logExpiration(e);
1302 		    iter.remove();
1303 		}
1304 
1305 		/*
1306 		 * Even if we have to send an event we assume that it
1307 		 * won't consume a slot in our schedule
1308 		 */
1309 		continue; 
1310 	    }
1311 
1312 	    if (e.endTime <= now && e.listener == null) {
1313 		// Lease has expired and no listener, just remove it.
1314 		logExpiration(e);
1315 		iter.remove();
1316 		continue;
1317 	    }
1318 
1319 	    /*
1320 	     * Make sure there aren't too many lease renewal threads
1321 	     * operating at the same time.
1322 	     */
1323 	    if (!canBatch(e)) {
1324 		/*
1325 		 * Find all renewals that start before we expect ours to
1326 		 * be done.
1327 		 */
1328 		for (Iterator listIter = calcList.iterator();
1329 		     listIter.hasNext(); )
1330 		{
1331 		    if (e.renew >=
1332 			((Entry) listIter.next()).actualRenew - renewalRTT)
1333 		    {
1334 			/*
1335 			 * This renewal starts after we expect ours to
1336 			 * be done.
1337 			 */
1338 			break;
1339 		    }
1340 		    listIter.remove();
1341 		}
1342 		if (calcList.size() == maxThreads) {
1343 		    /*
1344 		     * Too many renewals.  Move our actual renewal time
1345 		     * earlier so we'll probably be done before the last
1346 		     * one needs to start.  Remove that one, since it
1347 		     * won't overlap any earlier renewals.
1348 		     */
1349 		    Entry e1 = (Entry) calcList.remove(0);
1350 		    e.actualRenew = e1.actualRenew - renewalRTT;
1351 		}
1352 		calcList.add(e);
1353 	    }
1354 	}
1355 	calcList.clear();
1356 	long newWakeup = wakeupTime();
1357 	if (queuer == null) {
1358 	    if (newWakeup < Long.MAX_VALUE) {
1359 		queuer = new QueuerTask(newWakeup);
1360 		leaseRenewalExecutor.execute(queuer);
1361 	    }
1362 	} else if (newWakeup < queuer.wakeup ||
1363 		   (newWakeup == Long.MAX_VALUE && leaseInRenew.isEmpty()))
1364 	{
1365 	    notifyAll();
1366 	}
1367     }
1368 
1369     /**
1370      * Return true if e can be batched with another entry that expires
1371      * between e.renew - renewBatchTimeWindow and e.renew.
1372      */
1373     private boolean canBatch(Entry e) {
1374 	Iterator iter = leases.tailMap(e).values().iterator();
1375 	iter.next(); // skip e itself
1376 	while (iter.hasNext()) {
1377 	    Entry be = (Entry) iter.next();
1378 	    if (e.renew - be.renew > renewBatchTimeWindow)
1379 		break;
1380 	    if (e.canBatch(be))
1381 		return true;
1382 	}
1383 	return false;
1384     }
1385 
1386     /**
1387      * Find a lease entry, throw exception if not found or expired
1388      * normally
1389      */
1390     private Entry findEntry(Lease lease) throws UnknownLeaseException {
1391 	Entry e = findEntryDo(lease);
1392 	if (e != null &&
1393 	    (e.renew < e.endTime || System.currentTimeMillis() < e.endTime))
1394 	{
1395 	    return e;
1396 	}
1397 	throw new UnknownLeaseException();
1398     }
1399 
1400     /** Find a lease entry, or null */
1401     private Entry findEntryDo(Lease lease) {
1402 	Entry e = findLeaseFromIterator(leases.values().iterator(), lease);
1403 	if (e == null)
1404 	    e = findLeaseFromIterator(leaseInRenew.iterator(), lease);
1405 	return e;
1406     }
1407 
1408     /** Find a lease entry, or null */
1409     private static Entry findLeaseFromIterator(Iterator iter, Lease lease) {
1410 	while (iter.hasNext()) {
1411 	    Entry e = (Entry) iter.next();
1412 	    if (e.lease.equals(lease))
1413 		return e;
1414 	}
1415 	return null;
1416     }
1417 
1418     /** Notify the listener for each lease */
1419     private void tell(List bad) {
1420 	for (Iterator iter = bad.iterator(); iter.hasNext(); ) {
1421 	    Entry e = (Entry) iter.next();
1422 	    if (e.renewalsDone()) {
1423 		final DesiredExpirationListener del = 
1424 		    e.desiredExpirationListener();
1425 		if (del != null) {
1426 		    del.expirationReached(new LeaseRenewalEvent(this, e.lease,
1427 		        e.expiration, null));
1428 		}
1429 		continue; 
1430 	    }
1431 	    e.listener.notify(new LeaseRenewalEvent(this, e.lease,
1432 						    e.expiration, e.ex));
1433 	}
1434     }
1435 
1436     /**
1437      * Logs a lease expiration, distinguishing between expected
1438      * and premature expirations.
1439      *
1440      * @param e the <code>Entry</code> holding the lease
1441      */
1442     private void logExpiration(Entry e) {
1443 	if (e.renewalsDone()) {
1444 	    logger.log(Level.FINE,
1445 		       "Reached desired expiration for lease {0}",
1446 		       e.lease);
1447 	} else {
1448 	    logger.log(Levels.FAILED,
1449                "Lease '{'0'}' expired before reaching desired expiration of {0}",
1450                e.expiration);
1451 	}
1452     }
1453 		
1454     /**
1455      * Logs a throw. Use this method to log a throw when the log message needs
1456      * parameters.
1457      *
1458      * @param level the log level
1459      * @param sourceMethod name of the method where throw occurred
1460      * @param msg log message
1461      * @param params log message parameters
1462      * @param e exception thrown
1463      */
1464     private static void logThrow(Level level,
1465 				 String sourceMethod,
1466 				 String msg,
1467 				 Object[] params,
1468 				 Throwable e)
1469     {
1470 	LogRecord r = new LogRecord(level, msg);
1471 	r.setLoggerName(logger.getName());
1472 	r.setSourceClassName(LeaseRenewalManager.class.getName());
1473 	r.setSourceMethodName(sourceMethod);
1474 	r.setParameters(params);
1475 	r.setThrown(e);
1476 	logger.log(r);
1477     }
1478 
1479     /** Renew all of the leases (if multiple, all can be batched) */
1480     private void renewAll(List bList, long now) {
1481         Map lmeMap = null;
1482 	Throwable t = null;
1483 	List bad = null;
1484 
1485 	try {
1486 	    if (bList.size() == 1) {
1487 		Entry e = (Entry) bList.get(0);
1488 		logger.log(Level.FINE, "Renewing lease {0}", e.lease);
1489 		e.lease.renew(e.getRenewDuration(now));
1490 	    } else {
1491 		LeaseMap batchLeaseMap = createBatchLeaseMap(bList, now);
1492 		logger.log(Level.FINE, "Renewing leases {0}", batchLeaseMap);
1493 		batchLeaseMap.renewAll();
1494 	    }
1495 	} catch (LeaseMapException ex) {
1496 	    lmeMap = ex.exceptionMap;
1497 	    bad = new ArrayList(lmeMap.size());
1498 	} catch (Throwable ex) {
1499 	    t = ex;
1500 	    bad = new ArrayList(bList.size());  // They may all be bad
1501 	}
1502 
1503 	/*
1504 	 * For each lease we tried to renew determine the associated
1505 	 * exception (if any), and then ether add the lease back to
1506 	 * leases (if the renewal was successful), schedule a retry and
1507 	 * add back to leases (if the renewal was indefinite), or drop
1508 	 * the lease (by not adding it back to leases) and notify any
1509 	 * interested listeners.  In any event remove lease from the
1510 	 * list of leases being renewed.
1511 	 */
1512 
1513 	now = System.currentTimeMillis();
1514 	synchronized (this) {
1515 	    for (Iterator iter = bList.iterator(); iter.hasNext(); ) {
1516 		Entry e = (Entry) iter.next();
1517 
1518 		if (!removeLeaseInRenew(e))
1519 		    continue;
1520 
1521 		// Update the entries exception field 
1522 		if (bad == null) {
1523 		    e.ex = null;
1524 		} else {
1525 		    e.ex = (t != null) ? t : (Throwable) lmeMap.get(e.lease);
1526 		}
1527 
1528 		if (e.ex == null) {
1529 		    // No problems just put back in list
1530 		    insertEntry(e, now);
1531 		    continue;
1532 		} 
1533 
1534 		/*
1535 		 * Some sort of problem.  If definite don't put back
1536 		 * into leases and setup to notify the appropriate
1537 		 * listener, if indefinite schedule for a retry and put
1538 		 * back into leases
1539 		 */
1540 		final int cat = ThrowableConstants.retryable(e.ex);
1541 		if (cat == ThrowableConstants.INDEFINITE) {
1542 		    e.delayRenew();
1543 		    leases.put(e, e);
1544 		    if (logger.isLoggable(Levels.HANDLED)) {
1545 			logThrow(
1546 			    Levels.HANDLED, "renewAll",
1547 			    "Indefinite exception while renewing lease {0}",
1548 			    new Object[] { e.lease }, e.ex);
1549 		    }
1550 		} else {
1551 		    if (logger.isLoggable(Levels.FAILED)) {
1552 			logThrow(Levels.FAILED, "renewAll",
1553 				 "Lease renewal failed for lease {0}",
1554 				 new Object[] { e.lease }, e.ex);
1555 		    }
1556 		    if (e.listener != null) { 
1557 			/*
1558 			 * Note: For us ThrowableConstants.UNCATEGORIZED ==
1559 			 * definite
1560 			 */
1561 			bad.add(e);
1562 		    }	
1563 		}	
1564 	    }
1565 	    calcActualRenews(now);
1566 	}
1567 
1568 	if (bad != null)
1569 	    tell(bad);	
1570     }
1571 
1572     /** Create a LeaseMap for batch renewal */
1573     private static LeaseMap createBatchLeaseMap(List bList, long now) {
1574 	Iterator iter = bList.iterator();
1575 	Entry e = (Entry) iter.next();
1576 	LeaseMap batchLeaseMap =
1577 	    e.lease.createLeaseMap(e.getRenewDuration(now));
1578 	if (e.lease instanceof RemoteMethodControl &&
1579 	    batchLeaseMap instanceof RemoteMethodControl)
1580 	{
1581 	    batchLeaseMap = (LeaseMap)
1582 		((RemoteMethodControl) batchLeaseMap).setConstraints(
1583 		    ConstrainableProxyUtil.translateConstraints(
1584 			((RemoteMethodControl) e.lease).getConstraints(),
1585 			leaseToLeaseMapMethods));
1586 	}
1587 	while (iter.hasNext()) {
1588 	    e = (Entry) iter.next();
1589 	    batchLeaseMap.put(e.lease, Long.valueOf(e.getRenewDuration(now)));
1590 	}
1591 	return batchLeaseMap;
1592     }
1593 
1594     /** Remove from leaseInRenew, return true if removed */
1595     private boolean removeLeaseInRenew(Entry e) {
1596 	int index = leaseInRenew.indexOf(e); // avoid iterator cons
1597 	if (index < 0)
1598 	    return false;
1599 	leaseInRenew.remove(index);
1600 	return true;
1601     }
1602 
1603     /** Return the soonest actual renewal time */
1604     private long wakeupTime() {
1605 	if (leases.isEmpty())
1606 	    return Long.MAX_VALUE;
1607 	return ((Entry) leases.lastKey()).actualRenew;
1608     }
1609 
1610     private class QueuerTask implements Runnable {
1611 
1612 	/** When to next wake up and queue a new renew task */
1613 	private long wakeup;
1614 
1615 	QueuerTask(long wakeup) {
1616 	    this.wakeup = wakeup;
1617 	}
1618 
1619 
1620         public void run() {
1621 	    synchronized (LeaseRenewalManager.this) {
1622 		try {
1623 		    while (true) {
1624 			wakeup = wakeupTime();
1625 			if (wakeup == Long.MAX_VALUE && leaseInRenew.isEmpty())
1626 			    break;
1627 			final long now = System.currentTimeMillis();
1628 			long delta = wakeup - now;
1629 			if (delta <= 0) {
1630 			    leaseRenewalExecutor.execute(new RenewTask(now));
1631 			} else {
1632 			    LeaseRenewalManager.this.wait(delta);
1633 			}
1634 		    }
1635 		} catch (InterruptedException ex) {
1636 		}
1637 		queuer = null;
1638 	    }
1639 	}
1640     }
1641 }