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 }