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">•
150 * <th scope="col" align="left" colspan="2"><code>
151 * renewBatchTimeWindow</code>
152 * <tr valign="top"> <td> <th scope="row" align="right">
153 * Type: <td> <code>long</code>
154 * <tr valign="top"> <td> <th scope="row" align="right">
155 * Default: <td> <code>5 * 60 * 1000 // 5 minutes</code>
156 * <tr valign="top"> <td> <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">•
167 * <th scope="col" align="left" colspan="2"><code>
168 * roundTripTime</code>
169 * <tr valign="top"> <td> <th scope="row" align="right">
170 * Type: <td> <code>long</code>
171 * <tr valign="top"> <td> <th scope="row" align="right">
172 * Default: <td> <code>10 * 1000 // 10 seconds</code>
173 * <tr valign="top"> <td> <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">•
185 * <th scope="col" align="left" colspan="2"><code>
186 * executorService</code>
187 * <tr valign="top"> <td> <th scope="row" align="right">
188 * Type: <td> {@link ExecutorService}
189 * <tr valign="top"> <td> <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> <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 <= rtt * 2) {
249 * delta = rtt;
250 * } else if (delta <= rtt * 8) {
251 * delta /= 2;
252 * } else if (delta <= 1000 * 60 * 60 * 24 * 7) {
253 * delta /= 8;
254 * } else if (delta <= 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 > rtt) {
276 * if (delta <= rtt * 3) {
277 * delta = rtt;
278 * } else if (delta <= 1000 * 60 * 60) {
279 * delta /= 3;
280 * } else if (delta <= 1000 * 60 * 60 * 24) {
281 * delta = 1000 * 60 * 30;
282 * } else if (delta <= 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 <= 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 }