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