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