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 }