1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 module hunt.pool.impl.GenericKeyedObjectPool; 18 19 import hunt.pool.impl.BaseGenericObjectPool; 20 import hunt.pool.KeyedObjectPool; 21 import hunt.pool.KeyedPooledObjectFactory; 22 import hunt.pool.PoolUtils; 23 import hunt.pool.PooledObject; 24 import hunt.pool.PooledObjectState; 25 import hunt.pool.SwallowedExceptionListener; 26 27 import hunt.collection; 28 import hunt.Exceptions; 29 import hunt.Integer; 30 import hunt.concurrency.LinkedBlockingQueue; 31 32 import core.sync.rwmutex; 33 34 /** 35 * A configurable <code>KeyedObjectPool</code> implementation. 36 * <p> 37 * When coupled with the appropriate {@link KeyedPooledObjectFactory}, 38 * <code>GenericKeyedObjectPool</code> provides robust pooling functionality for 39 * keyed objects. A <code>GenericKeyedObjectPool</code> can be viewed as a map 40 * of sub-pools, keyed on the (unique) key values provided to the 41 * {@link #preparePool preparePool}, {@link #addObject addObject} or 42 * {@link #borrowObject borrowObject} methods. Each time a new key value is 43 * provided to one of these methods, a sub-new pool is created under the given 44 * key to be managed by the containing <code>GenericKeyedObjectPool.</code> 45 * <p> 46 * Note that the current implementation uses a ConcurrentHashMap which uses 47 * equals() to compare keys. 48 * This means that distinct instance keys must be distinguishable using equals. 49 * <p> 50 * Optionally, one may configure the pool to examine and possibly evict objects 51 * as they sit idle in the pool and to ensure that a minimum number of idle 52 * objects is maintained for each key. This is performed by an "idle object 53 * eviction" thread, which runs asynchronously. Caution should be used when 54 * configuring this optional feature. Eviction runs contend with client threads 55 * for access to objects in the pool, so if they run too frequently performance 56 * issues may result. 57 * <p> 58 * Implementation note: To prevent possible deadlocks, care has been taken to 59 * ensure that no call to a factory method will occur within a synchronization 60 * block. See POOL-125 and DBCP-44 for more information. 61 * <p> 62 * This class is intended to be thread-safe. 63 * 64 * @see GenericObjectPool 65 * 66 * @param <K> The type of keys maintained by this pool. 67 * @param <T> Type of element pooled in this pool. 68 * 69 */ 70 class GenericKeyedObjectPool(K, T) : BaseGenericObjectPool!(T), 71 KeyedObjectPool!(K, T), GenericKeyedObjectPoolMXBean!(K) { 72 73 /** 74 * Create a new <code>GenericKeyedObjectPool</code> using defaults from 75 * {@link GenericKeyedObjectPoolConfig}. 76 * @param factory the factory to be used to create entries 77 */ 78 this(KeyedPooledObjectFactory!(K,T) factory) { 79 this(factory, new GenericKeyedObjectPoolConfig!(T)()); 80 } 81 82 /** 83 * Create a new <code>GenericKeyedObjectPool</code> using a specific 84 * configuration. 85 * 86 * @param factory the factory to be used to create entries 87 * @param config The configuration to use for this pool instance. The 88 * configuration is used by value. Subsequent changes to 89 * the configuration object will not be reflected in the 90 * pool. 91 */ 92 this(KeyedPooledObjectFactory!(K, T) factory, 93 GenericKeyedObjectPoolConfig!(T) config) { 94 95 super(config, ONAME_BASE, config.getJmxNamePrefix()); 96 97 if (factory is null) { 98 // jmxUnregister(); // tidy up 99 throw new IllegalArgumentException("factory may not be null"); 100 } 101 this.factory = factory; 102 this.fairness = config.getFairness(); 103 104 setConfig(config); 105 } 106 107 /** 108 * Returns the limit on the number of object instances allocated by the pool 109 * (checked out or idle), per key. When the limit is reached, the sub-pool 110 * is said to be exhausted. A negative value indicates no limit. 111 * 112 * @return the limit on the number of active instances per key 113 * 114 * @see #setMaxTotalPerKey 115 */ 116 override 117 int getMaxTotalPerKey() { 118 return maxTotalPerKey; 119 } 120 121 /** 122 * Sets the limit on the number of object instances allocated by the pool 123 * (checked out or idle), per key. When the limit is reached, the sub-pool 124 * is said to be exhausted. A negative value indicates no limit. 125 * 126 * @param maxTotalPerKey the limit on the number of active instances per key 127 * 128 * @see #getMaxTotalPerKey 129 */ 130 void setMaxTotalPerKey(int maxTotalPerKey) { 131 this.maxTotalPerKey = maxTotalPerKey; 132 } 133 134 135 /** 136 * Returns the cap on the number of "idle" instances per key in the pool. 137 * If maxIdlePerKey is set too low on heavily loaded systems it is possible 138 * you will see objects being destroyed and almost immediately new objects 139 * being created. This is a result of the active threads momentarily 140 * returning objects faster than they are requesting them, causing the 141 * number of idle objects to rise above maxIdlePerKey. The best value for 142 * maxIdlePerKey for heavily loaded system will vary but the default is a 143 * good starting point. 144 * 145 * @return the maximum number of "idle" instances that can be held in a 146 * given keyed sub-pool or a negative value if there is no limit 147 * 148 * @see #setMaxIdlePerKey 149 */ 150 override 151 int getMaxIdlePerKey() { 152 return maxIdlePerKey; 153 } 154 155 /** 156 * Sets the cap on the number of "idle" instances per key in the pool. 157 * If maxIdlePerKey is set too low on heavily loaded systems it is possible 158 * you will see objects being destroyed and almost immediately new objects 159 * being created. This is a result of the active threads momentarily 160 * returning objects faster than they are requesting them, causing the 161 * number of idle objects to rise above maxIdlePerKey. The best value for 162 * maxIdlePerKey for heavily loaded system will vary but the default is a 163 * good starting point. 164 * 165 * @param maxIdlePerKey the maximum number of "idle" instances that can be 166 * held in a given keyed sub-pool. Use a negative value 167 * for no limit 168 * 169 * @see #getMaxIdlePerKey 170 */ 171 void setMaxIdlePerKey(int maxIdlePerKey) { 172 this.maxIdlePerKey = maxIdlePerKey; 173 } 174 175 /** 176 * Sets the target for the minimum number of idle objects to maintain in 177 * each of the keyed sub-pools. This setting only has an effect if it is 178 * positive and {@link #getTimeBetweenEvictionRunsMillis()} is greater than 179 * zero. If this is the case, an attempt is made to ensure that each 180 * sub-pool has the required minimum number of instances during idle object 181 * eviction runs. 182 * <p> 183 * If the configured value of minIdlePerKey is greater than the configured 184 * value for maxIdlePerKey then the value of maxIdlePerKey will be used 185 * instead. 186 * 187 * @param minIdlePerKey The minimum size of the each keyed pool 188 * 189 * @see #getMinIdlePerKey 190 * @see #getMaxIdlePerKey() 191 * @see #setTimeBetweenEvictionRunsMillis 192 */ 193 void setMinIdlePerKey(int minIdlePerKey) { 194 this.minIdlePerKey = minIdlePerKey; 195 } 196 197 /** 198 * Returns the target for the minimum number of idle objects to maintain in 199 * each of the keyed sub-pools. This setting only has an effect if it is 200 * positive and {@link #getTimeBetweenEvictionRunsMillis()} is greater than 201 * zero. If this is the case, an attempt is made to ensure that each 202 * sub-pool has the required minimum number of instances during idle object 203 * eviction runs. 204 * <p> 205 * If the configured value of minIdlePerKey is greater than the configured 206 * value for maxIdlePerKey then the value of maxIdlePerKey will be used 207 * instead. 208 * 209 * @return minimum size of the each keyed pool 210 * 211 * @see #setTimeBetweenEvictionRunsMillis 212 */ 213 override 214 int getMinIdlePerKey() { 215 int maxIdlePerKeySave = getMaxIdlePerKey(); 216 if (this.minIdlePerKey > maxIdlePerKeySave) { 217 return maxIdlePerKeySave; 218 } 219 return minIdlePerKey; 220 } 221 222 /** 223 * Sets the configuration. 224 * 225 * @param conf the new configuration to use. This is used by value. 226 * 227 * @see GenericKeyedObjectPoolConfig 228 */ 229 void setConfig(GenericKeyedObjectPoolConfig!(T) conf) { 230 super.setConfig(conf); 231 setMaxIdlePerKey(conf.getMaxIdlePerKey()); 232 setMaxTotalPerKey(conf.getMaxTotalPerKey()); 233 setMaxTotal(conf.getMaxTotal()); 234 setMinIdlePerKey(conf.getMinIdlePerKey()); 235 } 236 237 /** 238 * Obtain a reference to the factory used to create, destroy and validate 239 * the objects used by this pool. 240 * 241 * @return the factory 242 */ 243 KeyedPooledObjectFactory!(K, T) getFactory() { 244 return factory; 245 } 246 247 /** 248 * Equivalent to <code>{@link #borrowObject(Object, long) borrowObject}(key, 249 * {@link #getMaxWaitMillis()})</code>. 250 * <p> 251 * {@inheritDoc} 252 */ 253 override 254 T borrowObject(K key){ 255 return borrowObject(key, getMaxWaitMillis()); 256 } 257 258 /** 259 * Borrows an object from the sub-pool associated with the given key using 260 * the specified waiting time which only applies if 261 * {@link #getBlockWhenExhausted()} is true. 262 * <p> 263 * If there is one or more idle instances available in the sub-pool 264 * associated with the given key, then an idle instance will be selected 265 * based on the value of {@link #getLifo()}, activated and returned. If 266 * activation fails, or {@link #getTestOnBorrow() testOnBorrow} is set to 267 * <code>true</code> and validation fails, the instance is destroyed and the 268 * next available instance is examined. This continues until either a valid 269 * instance is returned or there are no more idle instances available. 270 * <p> 271 * If there are no idle instances available in the sub-pool associated with 272 * the given key, behavior depends on the {@link #getMaxTotalPerKey() 273 * maxTotalPerKey}, {@link #getMaxTotal() maxTotal}, and (if applicable) 274 * {@link #getBlockWhenExhausted()} and the value passed in to the 275 * <code>borrowMaxWaitMillis</code> parameter. If the number of instances checked 276 * out from the sub-pool under the given key is less than 277 * <code>maxTotalPerKey</code> and the total number of instances in 278 * circulation (under all keys) is less than <code>maxTotal</code>, a new 279 * instance is created, activated and (if applicable) validated and returned 280 * to the caller. If validation fails, a <code>NoSuchElementException</code> 281 * will be thrown. 282 * <p> 283 * If the associated sub-pool is exhausted (no available idle instances and 284 * no capacity to create new ones), this method will either block 285 * ({@link #getBlockWhenExhausted()} is true) or throw a 286 * <code>NoSuchElementException</code> 287 * ({@link #getBlockWhenExhausted()} is false). 288 * The length of time that this method will block when 289 * {@link #getBlockWhenExhausted()} is true is determined by the value 290 * passed in to the <code>borrowMaxWait</code> parameter. 291 * <p> 292 * When <code>maxTotal</code> is set to a positive value and this method is 293 * invoked when at the limit with no idle instances available under the requested 294 * key, an attempt is made to create room by clearing the oldest 15% of the 295 * elements from the keyed sub-pools. 296 * <p> 297 * When the pool is exhausted, multiple calling threads may be 298 * simultaneously blocked waiting for instances to become available. A 299 * "fairness" algorithm has been implemented to ensure that threads receive 300 * available instances in request arrival order. 301 * 302 * @param key pool key 303 * @param borrowMaxWaitMillis The time to wait in milliseconds for an object 304 * to become available 305 * 306 * @return object instance from the keyed pool 307 * 308 * @throws NoSuchElementException if a keyed object instance cannot be 309 * returned because the pool is exhausted. 310 * 311 * @throws Exception if a keyed object instance cannot be returned due to an 312 * error 313 */ 314 T borrowObject(K key, long borrowMaxWaitMillis){ 315 assertOpen(); 316 317 PooledObject!(T) p = null; 318 319 // Get local copy of current config so it is consistent for entire 320 // method execution 321 bool blockWhenExhausted = getBlockWhenExhausted(); 322 323 bool create; 324 long waitTime = DateTimeHelper.currentTimeMillis(); 325 ObjectDeque!(T) objectDeque = register(key); 326 327 try { 328 while (p is null) { 329 create = false; 330 p = objectDeque.getIdleObjects().pollFirst(); 331 if (p is null) { 332 p = create(key); 333 if (p !is null) { 334 create = true; 335 } 336 } 337 if (blockWhenExhausted) { 338 if (p is null) { 339 if (borrowMaxWaitMillis < 0) { 340 p = objectDeque.getIdleObjects().takeFirst(); 341 } else { 342 p = objectDeque.getIdleObjects().pollFirst( 343 borrowMaxWaitMillis, TimeUnit.MILLISECONDS); 344 } 345 } 346 if (p is null) { 347 throw new NoSuchElementException( 348 "Timeout waiting for idle object"); 349 } 350 } else { 351 if (p is null) { 352 throw new NoSuchElementException("Pool exhausted"); 353 } 354 } 355 if (!p.allocate()) { 356 p = null; 357 } 358 359 if (p !is null) { 360 try { 361 factory.activateObject(key, p); 362 } catch (Exception e) { 363 try { 364 destroy(key, p, true); 365 } catch (Exception e1) { 366 // Ignore - activation failure is more important 367 } 368 p = null; 369 if (create) { 370 NoSuchElementException nsee = new NoSuchElementException( 371 "Unable to activate object"); 372 nsee.initCause(e); 373 throw nsee; 374 } 375 } 376 if (p !is null && (getTestOnBorrow() || create && getTestOnCreate())) { 377 bool validate = false; 378 Throwable validationThrowable = null; 379 try { 380 validate = factory.validateObject(key, p); 381 } catch (Throwable t) { 382 PoolUtils.checkRethrow(t); 383 validationThrowable = t; 384 } 385 if (!validate) { 386 try { 387 destroy(key, p, true); 388 destroyedByBorrowValidationCount.incrementAndGet(); 389 } catch (Exception e) { 390 // Ignore - validation failure is more important 391 } 392 p = null; 393 if (create) { 394 NoSuchElementException nsee = new NoSuchElementException( 395 "Unable to validate object"); 396 nsee.initCause(validationThrowable); 397 throw nsee; 398 } 399 } 400 } 401 } 402 } 403 } finally { 404 deregister(key); 405 } 406 407 updateStatsBorrow(p, DateTimeHelper.currentTimeMillis() - waitTime); 408 409 return p.getObject(); 410 } 411 412 413 /** 414 * Returns an object to a keyed sub-pool. 415 * <p> 416 * If {@link #getMaxIdlePerKey() maxIdle} is set to a positive value and the 417 * number of idle instances under the given key has reached this value, the 418 * returning instance is destroyed. 419 * <p> 420 * If {@link #getTestOnReturn() testOnReturn} == true, the returning 421 * instance is validated before being returned to the idle instance sub-pool 422 * under the given key. In this case, if validation fails, the instance is 423 * destroyed. 424 * <p> 425 * Exceptions encountered destroying objects for any reason are swallowed 426 * but notified via a {@link SwallowedExceptionListener}. 427 * 428 * @param key pool key 429 * @param obj instance to return to the keyed pool 430 * 431 * @throws IllegalStateException if an object is returned to the pool that 432 * was not borrowed from it or if an object is 433 * returned to the pool multiple times 434 */ 435 override 436 void returnObject(K key, T obj) { 437 438 ObjectDeque!(T) objectDeque = poolMap.get(key); 439 440 PooledObject!(T) p = objectDeque.getAllObjects().get(new IdentityWrapper!T(obj)); 441 442 if (p is null) { 443 throw new IllegalStateException( 444 "Returned object not currently part of this pool"); 445 } 446 447 markReturningState(p); 448 449 long activeTime = p.getActiveTimeMillis(); 450 451 try { 452 if (getTestOnReturn() && !factory.validateObject(key, p)) { 453 try { 454 destroy(key, p, true); 455 } catch (Exception e) { 456 swallowException(e); 457 } 458 whenWaitersAddObject(key, objectDeque.idleObjects); 459 return; 460 } 461 462 try { 463 factory.passivateObject(key, p); 464 } catch (Exception e1) { 465 swallowException(e1); 466 try { 467 destroy(key, p, true); 468 } catch (Exception e) { 469 swallowException(e); 470 } 471 whenWaitersAddObject(key, objectDeque.idleObjects); 472 return; 473 } 474 475 if (!p.deallocate()) { 476 throw new IllegalStateException( 477 "Object has already been returned to this pool"); 478 } 479 480 int maxIdle = getMaxIdlePerKey(); 481 LinkedBlockingDeque!(PooledObject!(T)) idleObjects = 482 objectDeque.getIdleObjects(); 483 484 if (isClosed() || maxIdle > -1 && maxIdle <= idleObjects.size()) { 485 try { 486 destroy(key, p, true); 487 } catch (Exception e) { 488 swallowException(e); 489 } 490 } else { 491 if (getLifo()) { 492 idleObjects.addFirst(p); 493 } else { 494 idleObjects.addLast(p); 495 } 496 if (isClosed()) { 497 // Pool closed while object was being added to idle objects. 498 // Make sure the returned object is destroyed rather than left 499 // in the idle object pool (which would effectively be a leak) 500 clear(key); 501 } 502 } 503 } finally { 504 if (hasBorrowWaiters()) { 505 reuseCapacity(); 506 } 507 updateStatsReturn(activeTime); 508 } 509 } 510 511 /** 512 * Whether there is at least one thread waiting on this deque, add an pool object. 513 * @param key 514 * @param idleObjects 515 */ 516 private void whenWaitersAddObject(K key, LinkedBlockingDeque!(PooledObject!(T)) idleObjects) { 517 if (idleObjects.hasTakeWaiters()) { 518 try { 519 addObject(key); 520 } catch (Exception e) { 521 swallowException(e); 522 } 523 } 524 } 525 526 /** 527 * {@inheritDoc} 528 * <p> 529 * Activation of this method decrements the active count associated with 530 * the given keyed pool and attempts to destroy <code>obj.</code> 531 * 532 * @param key pool key 533 * @param obj instance to invalidate 534 * 535 * @throws Exception if an exception occurs destroying the 536 * object 537 * @throws IllegalStateException if obj does not belong to the pool 538 * under the given key 539 */ 540 override 541 void invalidateObject(K key, T obj){ 542 543 ObjectDeque!(T) objectDeque = poolMap.get(key); 544 545 PooledObject!(T) p = objectDeque.getAllObjects().get(new IdentityWrapper!T(obj)); 546 if (p is null) { 547 throw new IllegalStateException( 548 "Object not currently part of this pool"); 549 } 550 synchronized (p) { 551 if (p.getState() != PooledObjectState.INVALID) { 552 destroy(key, p, true); 553 } 554 } 555 if (objectDeque.idleObjects.hasTakeWaiters()) { 556 addObject(key); 557 } 558 } 559 560 561 /** 562 * Clears any objects sitting idle in the pool by removing them from the 563 * idle instance sub-pools and then invoking the configured 564 * PoolableObjectFactory's 565 * {@link KeyedPooledObjectFactory#destroyObject(Object, PooledObject)} 566 * method on each idle instance. 567 * <p> 568 * Implementation notes: 569 * <ul> 570 * <li>This method does not destroy or effect in any way instances that are 571 * checked out when it is invoked.</li> 572 * <li>Invoking this method does not prevent objects being returned to the 573 * idle instance pool, even during its execution. Additional instances may 574 * be returned while removed items are being destroyed.</li> 575 * <li>Exceptions encountered destroying idle instances are swallowed 576 * but notified via a {@link SwallowedExceptionListener}.</li> 577 * </ul> 578 */ 579 override 580 void clear() { 581 Iterator!(K) iter = poolMap.keySet().iterator(); 582 583 while (iter.hasNext()) { 584 clear(iter.next()); 585 } 586 } 587 588 589 /** 590 * Clears the specified sub-pool, removing all pooled instances 591 * corresponding to the given <code>key</code>. Exceptions encountered 592 * destroying idle instances are swallowed but notified via a 593 * {@link SwallowedExceptionListener}. 594 * 595 * @param key the key to clear 596 */ 597 override 598 void clear(K key) { 599 600 ObjectDeque!(T) objectDeque = register(key); 601 602 try { 603 LinkedBlockingDeque!(PooledObject!(T)) idleObjects = 604 objectDeque.getIdleObjects(); 605 606 PooledObject!(T) p = idleObjects.poll(); 607 608 while (p !is null) { 609 try { 610 destroy(key, p, true); 611 } catch (Exception e) { 612 swallowException(e); 613 } 614 p = idleObjects.poll(); 615 } 616 } finally { 617 deregister(key); 618 } 619 } 620 621 622 override 623 int getNumActive() { 624 return numTotal.get() - getNumIdle(); 625 } 626 627 628 override 629 int getNumIdle() { 630 Iterator!(ObjectDeque!(T)) iter = poolMap.values().iterator(); 631 int result = 0; 632 633 while (iter.hasNext()) { 634 result += iter.next().getIdleObjects().size(); 635 } 636 637 return result; 638 } 639 640 641 override 642 int getNumActive(K key) { 643 ObjectDeque!(T) objectDeque = poolMap.get(key); 644 if (objectDeque !is null) { 645 return objectDeque.getAllObjects().size() - 646 objectDeque.getIdleObjects().size(); 647 } 648 return 0; 649 } 650 651 652 override 653 int getNumIdle(K key) { 654 ObjectDeque!(T) objectDeque = poolMap.get(key); 655 return objectDeque !is null ? objectDeque.getIdleObjects().size() : 0; 656 } 657 658 659 /** 660 * Closes the keyed object pool. Once the pool is closed, 661 * {@link #borrowObject(Object)} will fail with IllegalStateException, but 662 * {@link #returnObject(Object, Object)} and 663 * {@link #invalidateObject(Object, Object)} will continue to work, with 664 * returned objects destroyed on return. 665 * <p> 666 * Destroys idle instances in the pool by invoking {@link #clear()}. 667 */ 668 override 669 void close() { 670 if (isClosed()) { 671 return; 672 } 673 674 synchronized (closeLock) { 675 if (isClosed()) { 676 return; 677 } 678 679 // Stop the evictor before the pool is closed since evict() calls 680 // assertOpen() 681 stopEvitor(); 682 683 closed = true; 684 // This clear removes any idle objects 685 clear(); 686 687 // jmxUnregister(); 688 689 // Release any threads that were waiting for an object 690 Iterator!(ObjectDeque!(T)) iter = poolMap.values().iterator(); 691 while (iter.hasNext()) { 692 iter.next().getIdleObjects().interuptTakeWaiters(); 693 } 694 // This clear cleans up the keys now any waiting threads have been 695 // interrupted 696 clear(); 697 } 698 } 699 700 701 /** 702 * Clears oldest 15% of objects in pool. The method sorts the objects into 703 * a TreeMap and then iterates the first 15% for removal. 704 */ 705 void clearOldest() { 706 707 // build sorted map of idle objects 708 Map!(PooledObject!(T), K) map = new TreeMap!(PooledObject!(T), K)(); 709 710 foreach (K k, ObjectDeque!(T) deque; poolMap) { 711 // Protect against possible NPE if key has been removed in another 712 // thread. Not worth locking the keys while this loop completes. 713 if (deque !is null) { 714 LinkedBlockingDeque!(PooledObject!(T)) idleObjects = 715 deque.getIdleObjects(); 716 foreach (PooledObject!(T) p ; idleObjects) { 717 // each item into the map using the PooledObject object as the 718 // key. It then gets sorted based on the idle time 719 map.put(p, k); 720 } 721 } 722 } 723 724 // Now iterate created map and kill the first 15% plus one to account 725 // for zero 726 int itemsToRemove = (cast(int) (map.size() * 0.15)) + 1; 727 // Iterator<Map.Entry!(PooledObject!(T), K)> iter = 728 // map.entrySet().iterator(); 729 730 // while (iter.hasNext() && itemsToRemove > 0) { 731 // Map.Entry!(PooledObject!(T), K) entry = iter.next(); 732 // // kind of backwards on naming. In the map, each key is the 733 // // PooledObject because it has the ordering with the timestamp 734 // // value. Each value that the key references is the key of the 735 // // list it belongs to. 736 // K key = entry.getValue(); 737 // PooledObject!(T) p = entry.getKey(); 738 // // Assume the destruction succeeds 739 // bool destroyed = true; 740 // try { 741 // destroyed = destroy(key, p, false); 742 // } catch (Exception e) { 743 // swallowException(e); 744 // } 745 // if (destroyed) { 746 // itemsToRemove--; 747 // } 748 // } 749 implementationMissing(false); 750 } 751 752 /** 753 * Attempt to create one new instance to serve from the most heavily 754 * loaded pool that can add a new instance. 755 * 756 * This method exists to ensure liveness in the pool when threads are 757 * parked waiting and capacity to create instances under the requested keys 758 * subsequently becomes available. 759 * 760 * This method is not guaranteed to create an instance and its selection 761 * of the most loaded pool that can create an instance may not always be 762 * correct, since it does not lock the pool and instances may be created, 763 * borrowed, returned or destroyed by other threads while it is executing. 764 */ 765 private void reuseCapacity() { 766 int maxTotalPerKeySave = getMaxTotalPerKey(); 767 768 // Find the most loaded pool that could take a new instance 769 int maxQueueLength = 0; 770 LinkedBlockingDeque!(PooledObject!(T)) mostLoaded = null; 771 K loadedKey = null; 772 foreach (K k, ObjectDeque!(T) deque; poolMap) { 773 if (deque !is null) { 774 LinkedBlockingDeque!(PooledObject!(T)) pool = deque.getIdleObjects(); 775 int queueLength = pool.getTakeQueueLength(); 776 if (getNumActive(k) < maxTotalPerKeySave && queueLength > maxQueueLength) { 777 maxQueueLength = queueLength; 778 mostLoaded = pool; 779 loadedKey = k; 780 } 781 } 782 } 783 784 // Attempt to add an instance to the most loaded pool 785 if (mostLoaded !is null) { 786 register(loadedKey); 787 try { 788 PooledObject!(T) p = create(loadedKey); 789 if (p !is null) { 790 addIdleObject(loadedKey, p); 791 } 792 } catch (Exception e) { 793 swallowException(e); 794 } finally { 795 deregister(loadedKey); 796 } 797 } 798 } 799 800 /** 801 * Checks to see if there are any threads currently waiting to borrow 802 * objects but are blocked waiting for more objects to become available. 803 * 804 * @return {@code true} if there is at least one thread waiting otherwise 805 * {@code false} 806 */ 807 private bool hasBorrowWaiters() { 808 foreach (ObjectDeque!(T) deque; poolMap.byValue) { 809 if (deque !is null) { 810 LinkedBlockingDeque!(PooledObject!(T)) pool = 811 deque.getIdleObjects(); 812 if(pool.hasTakeWaiters()) { 813 return true; 814 } 815 } 816 } 817 return false; 818 } 819 820 821 /** 822 * {@inheritDoc} 823 * <p> 824 * Successive activations of this method examine objects in keyed sub-pools 825 * in sequence, cycling through the keys and examining objects in 826 * oldest-to-youngest order within the keyed sub-pools. 827 */ 828 override 829 void evict(){ 830 assertOpen(); 831 832 if (getNumIdle() == 0) { 833 return; 834 } 835 836 PooledObject!(T) underTest = null; 837 EvictionPolicy!(T) evictionPolicy = getEvictionPolicy(); 838 839 synchronized (evictionLock) { 840 EvictionConfig evictionConfig = new EvictionConfig( 841 getMinEvictableIdleTimeMillis(), 842 getSoftMinEvictableIdleTimeMillis(), 843 getMinIdlePerKey()); 844 845 bool testWhileIdle = getTestWhileIdle(); 846 847 for (int i = 0, m = getNumTests(); i < m; i++) { 848 if(evictionIterator is null || !evictionIterator.hasNext()) { 849 if (evictionKeyIterator is null || 850 !evictionKeyIterator.hasNext()) { 851 List!(K) keyCopy = new ArrayList!K(); 852 Lock readLock = keyLock.readLock(); 853 readLock.lock(); 854 try { 855 keyCopy.addAll(poolKeyList); 856 } finally { 857 readLock.unlock(); 858 } 859 evictionKeyIterator = keyCopy.iterator(); 860 } 861 while (evictionKeyIterator.hasNext()) { 862 evictionKey = evictionKeyIterator.next(); 863 ObjectDeque!(T) objectDeque = poolMap.get(evictionKey); 864 if (objectDeque is null) { 865 continue; 866 } 867 868 Deque!(PooledObject!(T)) idleObjects = objectDeque.getIdleObjects(); 869 evictionIterator = new EvictionIterator(idleObjects, getLifo()); 870 if (evictionIterator.hasNext()) { 871 break; 872 } 873 evictionIterator = null; 874 } 875 } 876 if (evictionIterator is null) { 877 // Pools exhausted 878 return; 879 } 880 Deque!(PooledObject!(T)) idleObjects; 881 try { 882 underTest = evictionIterator.next(); 883 idleObjects = evictionIterator.getIdleObjects(); 884 } catch (NoSuchElementException nsee) { 885 // Object was borrowed in another thread 886 // Don't count this as an eviction test so reduce i; 887 i--; 888 evictionIterator = null; 889 continue; 890 } 891 892 if (!underTest.startEvictionTest()) { 893 // Object was borrowed in another thread 894 // Don't count this as an eviction test so reduce i; 895 i--; 896 continue; 897 } 898 899 // User provided eviction policy could throw all sorts of 900 // crazy exceptions. Protect against such an exception 901 // killing the eviction thread. 902 bool evict; 903 try { 904 evict = evictionPolicy.evict(evictionConfig, underTest, 905 poolMap.get(evictionKey).getIdleObjects().size()); 906 } catch (Throwable t) { 907 // Slightly convoluted as SwallowedExceptionListener 908 // uses Exception rather than Throwable 909 PoolUtils.checkRethrow(t); 910 swallowException(new Exception(t)); 911 // Don't evict on error conditions 912 evict = false; 913 } 914 915 if (evict) { 916 destroy(evictionKey, underTest, true); 917 destroyedByEvictorCount.incrementAndGet(); 918 } else { 919 if (testWhileIdle) { 920 bool active = false; 921 try { 922 factory.activateObject(evictionKey, underTest); 923 active = true; 924 } catch (Exception e) { 925 destroy(evictionKey, underTest, true); 926 destroyedByEvictorCount.incrementAndGet(); 927 } 928 if (active) { 929 if (!factory.validateObject(evictionKey, underTest)) { 930 destroy(evictionKey, underTest, true); 931 destroyedByEvictorCount.incrementAndGet(); 932 } else { 933 try { 934 factory.passivateObject(evictionKey, underTest); 935 } catch (Exception e) { 936 destroy(evictionKey, underTest, true); 937 destroyedByEvictorCount.incrementAndGet(); 938 } 939 } 940 } 941 } 942 if (!underTest.endEvictionTest(idleObjects)) { 943 // TODO - May need to add code here once additional 944 // states are used 945 } 946 } 947 } 948 } 949 } 950 951 /** 952 * Create a new pooled object. 953 * 954 * @param key Key associated with new pooled object 955 * 956 * @return The new, wrapped pooled object 957 * 958 * @throws Exception If the objection creation fails 959 */ 960 private PooledObject!(T) create(K key){ 961 int maxTotalPerKeySave = getMaxTotalPerKey(); // Per key 962 if (maxTotalPerKeySave < 0) { 963 maxTotalPerKeySave = Integer.MAX_VALUE; 964 } 965 int maxTotal = getMaxTotal(); // All keys 966 967 ObjectDeque!(T) objectDeque = poolMap.get(key); 968 969 // Check against the overall limit 970 bool loop = true; 971 972 while (loop) { 973 int newNumTotal = numTotal.incrementAndGet(); 974 if (maxTotal > -1 && newNumTotal > maxTotal) { 975 numTotal.decrementAndGet(); 976 if (getNumIdle() == 0) { 977 return null; 978 } 979 clearOldest(); 980 } else { 981 loop = false; 982 } 983 } 984 985 // Flag that indicates if create should: 986 // - TRUE: call the factory to create an object 987 // - FALSE: return null 988 // - null: loop and re-test the condition that determines whether to 989 // call the factory 990 Boolean create = null; 991 while (create is null) { 992 synchronized (objectDeque.makeObjectCountLock) { 993 long newCreateCount = objectDeque.getCreateCount().incrementAndGet(); 994 // Check against the per key limit 995 if (newCreateCount > maxTotalPerKeySave) { 996 // The key is currently at capacity or in the process of 997 // making enough new objects to take it to capacity. 998 objectDeque.getCreateCount().decrementAndGet(); 999 if (objectDeque.makeObjectCount == 0) { 1000 // There are no makeObject() calls in progress for this 1001 // key so the key is at capacity. Do not attempt to 1002 // create a new object. Return and wait for an object to 1003 // be returned. 1004 create = Boolean.FALSE; 1005 } else { 1006 // There are makeObject() calls in progress that might 1007 // bring the pool to capacity. Those calls might also 1008 // fail so wait until they complete and then re-test if 1009 // the pool is at capacity or not. 1010 objectDeque.makeObjectCountLock.wait(); 1011 } 1012 } else { 1013 // The pool is not at capacity. Create a new object. 1014 objectDeque.makeObjectCount++; 1015 create = Boolean.TRUE; 1016 } 1017 } 1018 } 1019 1020 if (!create.booleanValue()) { 1021 numTotal.decrementAndGet(); 1022 return null; 1023 } 1024 1025 PooledObject!(T) p = null; 1026 try { 1027 p = factory.makeObject(key); 1028 } catch (Exception e) { 1029 numTotal.decrementAndGet(); 1030 objectDeque.getCreateCount().decrementAndGet(); 1031 throw e; 1032 } finally { 1033 synchronized (objectDeque.makeObjectCountLock) { 1034 objectDeque.makeObjectCount--; 1035 objectDeque.makeObjectCountLock.notifyAll(); 1036 } 1037 } 1038 1039 createdCount.incrementAndGet(); 1040 objectDeque.getAllObjects().put(new IdentityWrapper!T(p.getObject()), p); 1041 return p; 1042 } 1043 1044 /** 1045 * Destroy the wrapped, pooled object. 1046 * 1047 * @param key The key associated with the object to destroy. 1048 * @param toDestroy The wrapped object to be destroyed 1049 * @param always Should the object be destroyed even if it is not currently 1050 * in the set of idle objects for the given key 1051 * @return {@code true} if the object was destroyed, otherwise {@code false} 1052 * @throws Exception If the object destruction failed 1053 */ 1054 private bool destroy(K key, PooledObject!(T) toDestroy, bool always) { 1055 1056 ObjectDeque!(T) objectDeque = register(key); 1057 1058 try { 1059 bool isIdle = objectDeque.getIdleObjects().remove(toDestroy); 1060 1061 if (isIdle || always) { 1062 objectDeque.getAllObjects().remove(new IdentityWrapper!T(toDestroy.getObject())); 1063 toDestroy.invalidate(); 1064 1065 try { 1066 factory.destroyObject(key, toDestroy); 1067 } finally { 1068 objectDeque.getCreateCount().decrementAndGet(); 1069 destroyedCount.incrementAndGet(); 1070 numTotal.decrementAndGet(); 1071 } 1072 return true; 1073 } 1074 return false; 1075 } finally { 1076 deregister(key); 1077 } 1078 } 1079 1080 1081 /** 1082 * Register the use of a key by an object. 1083 * <p> 1084 * register() and deregister() must always be used as a pair. 1085 * 1086 * @param k The key to register 1087 * 1088 * @return The objects currently associated with the given key. If this 1089 * method returns without throwing an exception then it will never 1090 * return null. 1091 */ 1092 private ObjectDeque!(T) register(K k) { 1093 Lock lock = keyLock.readLock(); 1094 ObjectDeque!(T) objectDeque = null; 1095 try { 1096 lock.lock(); 1097 objectDeque = poolMap.get(k); 1098 if (objectDeque is null) { 1099 // Upgrade to write lock 1100 lock.unlock(); 1101 lock = keyLock.writeLock(); 1102 lock.lock(); 1103 objectDeque = poolMap.get(k); 1104 if (objectDeque is null) { 1105 objectDeque = new ObjectDeque!T(fairness); 1106 objectDeque.getNumInterested().incrementAndGet(); 1107 // NOTE: Keys must always be added to both poolMap and 1108 // poolKeyList at the same time while protected by 1109 // keyLock.writeLock() 1110 poolMap.put(k, objectDeque); 1111 poolKeyList.add(k); 1112 } else { 1113 objectDeque.getNumInterested().incrementAndGet(); 1114 } 1115 } else { 1116 objectDeque.getNumInterested().incrementAndGet(); 1117 } 1118 } finally { 1119 lock.unlock(); 1120 } 1121 return objectDeque; 1122 } 1123 1124 /** 1125 * De-register the use of a key by an object. 1126 * <p> 1127 * register() and deregister() must always be used as a pair. 1128 * 1129 * @param k The key to de-register 1130 */ 1131 private void deregister(K k) { 1132 Lock lock = keyLock.readLock(); 1133 ObjectDeque!(T) objectDeque; 1134 try { 1135 lock.lock(); 1136 objectDeque = poolMap.get(k); 1137 long numInterested = objectDeque.getNumInterested().decrementAndGet(); 1138 if (numInterested == 0 && objectDeque.getCreateCount().get() == 0) { 1139 // Potential to remove key 1140 // Upgrade to write lock 1141 lock.unlock(); 1142 lock = keyLock.writeLock(); 1143 lock.lock(); 1144 if (objectDeque.getCreateCount().get() == 0 && objectDeque.getNumInterested().get() == 0) { 1145 // NOTE: Keys must always be removed from both poolMap and 1146 // poolKeyList at the same time while protected by 1147 // keyLock.writeLock() 1148 poolMap.remove(k); 1149 poolKeyList.remove(k); 1150 } 1151 } 1152 } finally { 1153 lock.unlock(); 1154 } 1155 } 1156 1157 override 1158 void ensureMinIdle(){ 1159 int minIdlePerKeySave = getMinIdlePerKey(); 1160 if (minIdlePerKeySave < 1) { 1161 return; 1162 } 1163 1164 foreach(K k ; poolMap.keySet()) { 1165 ensureMinIdle(k); 1166 } 1167 } 1168 1169 /** 1170 * Ensure that the configured number of minimum idle objects is available in 1171 * the pool for the given key. 1172 * 1173 * @param key The key to check for idle objects 1174 * 1175 * @throws Exception If a new object is required and cannot be created 1176 */ 1177 private void ensureMinIdle(K key){ 1178 // Calculate current pool objects 1179 ObjectDeque!(T) objectDeque = poolMap.get(key); 1180 1181 // objectDeque is null is OK here. It is handled correctly by both 1182 // methods called below. 1183 1184 // this method isn't synchronized so the 1185 // calculateDeficit is done at the beginning 1186 // as a loop limit and a second time inside the loop 1187 // to stop when another thread already returned the 1188 // needed objects 1189 int deficit = calculateDeficit(objectDeque); 1190 1191 for (int i = 0; i < deficit && calculateDeficit(objectDeque) > 0; i++) { 1192 addObject(key); 1193 // If objectDeque was null, it won't be any more. Obtain a reference 1194 // to it so the deficit can be correctly calculated. It needs to 1195 // take account of objects created in other threads. 1196 if (objectDeque is null) { 1197 objectDeque = poolMap.get(key); 1198 } 1199 } 1200 } 1201 1202 /** 1203 * Create an object using the {@link KeyedPooledObjectFactory#makeObject 1204 * factory}, passivate it, and then place it in the idle object pool. 1205 * <code>addObject</code> is useful for "pre-loading" a pool with idle 1206 * objects. 1207 * 1208 * @param key the key a new instance should be added to 1209 * 1210 * @throws Exception when {@link KeyedPooledObjectFactory#makeObject} 1211 * fails. 1212 */ 1213 override 1214 void addObject(K key){ 1215 assertOpen(); 1216 register(key); 1217 try { 1218 PooledObject!(T) p = create(key); 1219 addIdleObject(key, p); 1220 } finally { 1221 deregister(key); 1222 } 1223 } 1224 1225 /** 1226 * Add an object to the set of idle objects for a given key. 1227 * 1228 * @param key The key to associate with the idle object 1229 * @param p The wrapped object to add. 1230 * 1231 * @throws Exception If the associated factory fails to passivate the object 1232 */ 1233 private void addIdleObject(K key, PooledObject!(T) p){ 1234 1235 if (p !is null) { 1236 factory.passivateObject(key, p); 1237 LinkedBlockingDeque!(PooledObject!(T)) idleObjects = 1238 poolMap.get(key).getIdleObjects(); 1239 if (getLifo()) { 1240 idleObjects.addFirst(p); 1241 } else { 1242 idleObjects.addLast(p); 1243 } 1244 } 1245 } 1246 1247 /** 1248 * Registers a key for pool control and ensures that 1249 * {@link #getMinIdlePerKey()} idle instances are created. 1250 * 1251 * @param key - The key to register for pool control. 1252 * 1253 * @throws Exception If the associated factoryexception 1254 */ 1255 void preparePool(K key){ 1256 int minIdlePerKeySave = getMinIdlePerKey(); 1257 if (minIdlePerKeySave < 1) { 1258 return; 1259 } 1260 ensureMinIdle(key); 1261 } 1262 1263 /** 1264 * Calculate the number of objects to test in a run of the idle object 1265 * evictor. 1266 * 1267 * @return The number of objects to test for validity 1268 */ 1269 private int getNumTests() { 1270 int totalIdle = getNumIdle(); 1271 int numTests = getNumTestsPerEvictionRun(); 1272 implementationMissing(false); 1273 return 0; 1274 // if (numTests >= 0) { 1275 // return Math.min(numTests, totalIdle); 1276 // } 1277 // return cast(int)(Math.ceil(totalIdle/Math.abs((double)numTests))); 1278 } 1279 1280 /** 1281 * Calculate the number of objects that need to be created to attempt to 1282 * maintain the minimum number of idle objects while not exceeded the limits 1283 * on the maximum number of objects either per key or totally. 1284 * 1285 * @param objectDeque The set of objects to check 1286 * 1287 * @return The number of new objects to create 1288 */ 1289 private int calculateDeficit(ObjectDeque!(T) objectDeque) { 1290 1291 if (objectDeque is null) { 1292 return getMinIdlePerKey(); 1293 } 1294 1295 // Used more than once so keep a local copy so the value is consistent 1296 int maxTotal = getMaxTotal(); 1297 int maxTotalPerKeySave = getMaxTotalPerKey(); 1298 1299 int objectDefecit = 0; 1300 1301 // Calculate no of objects needed to be created, in order to have 1302 // the number of pooled objects < maxTotalPerKey(); 1303 objectDefecit = getMinIdlePerKey() - objectDeque.getIdleObjects().size(); 1304 if (maxTotalPerKeySave > 0) { 1305 int growLimit = Math.max(0, 1306 maxTotalPerKeySave - objectDeque.getIdleObjects().size()); 1307 objectDefecit = Math.min(objectDefecit, growLimit); 1308 } 1309 1310 // Take the maxTotal limit into account 1311 if (maxTotal > 0) { 1312 int growLimit = Math.max(0, maxTotal - getNumActive() - getNumIdle()); 1313 objectDefecit = Math.min(objectDefecit, growLimit); 1314 } 1315 1316 return objectDefecit; 1317 } 1318 1319 1320 //--- JMX support ---------------------------------------------------------- 1321 1322 override 1323 Map!(string,Integer) getNumActivePerKey() { 1324 HashMap!(string,Integer) result = new HashMap!(string,Integer)(); 1325 implementationMissing(false); 1326 1327 // Iterator!(Entry!(K,ObjectDeque!(T))) iter = poolMap.entrySet().iterator(); 1328 // while (iter.hasNext()) { 1329 // Entry!(K,ObjectDeque!(T)) entry = iter.next(); 1330 // if (entry !is null) { 1331 // K key = entry.getKey(); 1332 // ObjectDeque!(T) objectDequeue = entry.getValue(); 1333 // if (key !is null && objectDequeue !is null) { 1334 // result.put(key.toString(), Integer.valueOf( 1335 // objectDequeue.getAllObjects().size() - 1336 // objectDequeue.getIdleObjects().size())); 1337 // } 1338 // } 1339 // } 1340 return result; 1341 } 1342 1343 /** 1344 * Return an estimate of the number of threads currently blocked waiting for 1345 * an object from the pool. This is intended for monitoring only, not for 1346 * synchronization control. 1347 * 1348 * @return The estimate of the number of threads currently blocked waiting 1349 * for an object from the pool 1350 */ 1351 override 1352 int getNumWaiters() { 1353 int result = 0; 1354 1355 if (getBlockWhenExhausted()) { 1356 Iterator!(ObjectDeque!(T)) iter = poolMap.values().iterator(); 1357 1358 while (iter.hasNext()) { 1359 // Assume no overflow 1360 result += iter.next().getIdleObjects().getTakeQueueLength(); 1361 } 1362 } 1363 1364 return result; 1365 } 1366 1367 /** 1368 * Return an estimate of the number of threads currently blocked waiting for 1369 * an object from the pool for each key. This is intended for 1370 * monitoring only, not for synchronization control. 1371 * 1372 * @return The estimate of the number of threads currently blocked waiting 1373 * for an object from the pool for each key 1374 */ 1375 override 1376 Map!(string,Integer) getNumWaitersByKey() { 1377 Map!(string,Integer) result = new HashMap!(string,Integer)(); 1378 1379 foreach (K k, ObjectDeque!(T) deque; poolMap) { 1380 if (deque !is null) { 1381 if (getBlockWhenExhausted()) { 1382 result.put(k.toString(), Integer.valueOf( 1383 deque.getIdleObjects().getTakeQueueLength())); 1384 } else { 1385 result.put(k.toString(), Integer.valueOf(0)); 1386 } 1387 } 1388 } 1389 return result; 1390 } 1391 1392 /** 1393 * Provides information on all the objects in the pool, both idle (waiting 1394 * to be borrowed) and active (currently borrowed). 1395 * <p> 1396 * Note: This is named listAllObjects so it is presented as an operation via 1397 * JMX. That means it won't be invoked unless the explicitly requested 1398 * whereas all attributes will be automatically requested when viewing the 1399 * attributes for an object in a tool like JConsole. 1400 * 1401 * @return Information grouped by key on all the objects in the pool 1402 */ 1403 override 1404 Map!(string,List!(DefaultPooledObjectInfo)) listAllObjects() { 1405 Map!(string, List!(DefaultPooledObjectInfo)) result = 1406 new HashMap!(string, List!(DefaultPooledObjectInfo))(); 1407 1408 foreach (K k, ObjectDeque!(T) deque; poolMap) { 1409 if (deque !is null) { 1410 List!(DefaultPooledObjectInfo) list = 1411 new ArrayList!(DefaultPooledObjectInfo)(); 1412 result.put(k.toString(), list); 1413 foreach (PooledObject!(T) p ; deque.getAllObjects().values()) { 1414 list.add(new DefaultPooledObjectInfo(p)); 1415 } 1416 } 1417 } 1418 return result; 1419 } 1420 1421 1422 //--- inner classes ---------------------------------------------- 1423 1424 /** 1425 * Maintains information on the per key queue for a given key. 1426 * 1427 * @param <S> type of objects in the pool 1428 */ 1429 private class ObjectDeque(S) { 1430 1431 private LinkedBlockingDeque!(PooledObject!(S)) idleObjects; 1432 1433 /* 1434 * Number of instances created - number destroyed. 1435 * Invariant: createCount <= maxTotalPerKey 1436 */ 1437 private shared int createCount = 0; 1438 1439 private long makeObjectCount = 0; 1440 private Object makeObjectCountLock; // = new Object(); 1441 1442 /* 1443 * The map is keyed on pooled instances, wrapped to ensure that 1444 * they work properly as keys. 1445 */ 1446 private Map!(IdentityWrapper!(S), PooledObject!(S)) allObjects; 1447 1448 /* 1449 * Number of threads with registered interest in this key. 1450 * register(K) increments this counter and deRegister(K) decrements it. 1451 * Invariant: empty keyed pool will not be dropped unless numInterested 1452 * is 0. 1453 */ 1454 private shared long numInterested = 0; 1455 1456 /** 1457 * Create a new ObjecDeque with the given fairness policy. 1458 * @param fairness true means client threads waiting to borrow / return instances 1459 * will be served as if waiting in a FIFO queue. 1460 */ 1461 this(bool fairness) { 1462 makeObjectCountLock = new Object(); 1463 // allObjects = new ConcurrentHashMap!(IdentityWrapper!(S), PooledObject!(S))(); 1464 allObjects = new HashMap!(IdentityWrapper!(S), PooledObject!(S))(); 1465 idleObjects = new LinkedBlockingDeque!(PooledObject!(S))(fairness); 1466 // poolMap = new ConcurrentHashMap<>(); 1467 poolMap = new HashMap!(K,ObjectDeque!(T))(); 1468 poolKeyList = new ArrayList!(K)(); 1469 keyLock = new ReadWriteMutex(); 1470 } 1471 1472 /** 1473 * Obtain the idle objects for the current key. 1474 * 1475 * @return The idle objects 1476 */ 1477 LinkedBlockingDeque!(PooledObject!(S)) getIdleObjects() { 1478 return idleObjects; 1479 } 1480 1481 /** 1482 * Obtain the count of the number of objects created for the current 1483 * key. 1484 * 1485 * @return The number of objects created for this key 1486 */ 1487 AtomicInteger getCreateCount() { 1488 return createCount; 1489 } 1490 1491 /** 1492 * Obtain the number of threads with an interest registered in this key. 1493 * 1494 * @return The number of threads with a registered interest in this key 1495 */ 1496 AtomicLong getNumInterested() { 1497 return numInterested; 1498 } 1499 1500 /** 1501 * Obtain all the objects for the current key. 1502 * 1503 * @return All the objects 1504 */ 1505 Map!(IdentityWrapper!(S), PooledObject!(S)) getAllObjects() { 1506 return allObjects; 1507 } 1508 1509 override 1510 string toString() { 1511 StringBuilder builder = new StringBuilder(); 1512 builder.append("ObjectDeque [idleObjects="); 1513 builder.append(idleObjects); 1514 builder.append(", createCount="); 1515 builder.append(createCount); 1516 builder.append(", allObjects="); 1517 builder.append(allObjects); 1518 builder.append(", numInterested="); 1519 builder.append(numInterested); 1520 builder.append("]"); 1521 return builder.toString(); 1522 } 1523 1524 } 1525 1526 //--- configuration attributes --------------------------------------------- 1527 private shared int maxIdlePerKey = 1528 GenericKeyedObjectPoolConfig.DEFAULT_MAX_IDLE_PER_KEY; 1529 private shared int minIdlePerKey = 1530 GenericKeyedObjectPoolConfig.DEFAULT_MIN_IDLE_PER_KEY; 1531 private shared int maxTotalPerKey = 1532 GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY; 1533 private KeyedPooledObjectFactory!(K,T) factory; 1534 private bool fairness; 1535 1536 1537 //--- internal attributes -------------------------------------------------- 1538 1539 /* 1540 * My hash of sub-pools (ObjectQueue). The list of keys <b>must</b> be kept 1541 * in step with {@link #poolKeyList} using {@link #keyLock} to ensure any 1542 * changes to the list of current keys is made in a thread-safe manner. 1543 */ 1544 private Map!(K,ObjectDeque!(T)) poolMap; 1545 // new ConcurrentHashMap<>(); // @GuardedBy("keyLock") for write access (and some read access) 1546 /* 1547 * List of pool keys - used to control eviction order. The list of keys 1548 * <b>must</b> be kept in step with {@link #poolMap} using {@link #keyLock} 1549 * to ensure any changes to the list of current keys is made in a 1550 * thread-safe manner. 1551 */ 1552 private List!(K) poolKeyList; // = new ArrayList<>(); // @GuardedBy("keyLock") 1553 // private ReadWriteLock keyLock = new ReentrantReadWriteLock(true); 1554 private ReadWriteMutex keyLock; // = new ReentrantReadWriteLock(true); 1555 /* 1556 * The combined count of the currently active objects for all keys and those 1557 * in the process of being created. Under load, it may exceed 1558 * {@link #maxTotal} but there will never be more than {@link #maxTotal} 1559 * created at any one time. 1560 */ 1561 private shared int numTotal = 0; // new AtomicInteger(0); 1562 private Iterator!(K) evictionKeyIterator = null; // @GuardedBy("evictionLock") 1563 private K evictionKey = null; // @GuardedBy("evictionLock") 1564 1565 // JMX specific attributes 1566 private enum string ONAME_BASE = 1567 "hunt.pool:type=GenericKeyedObjectPool,name="; 1568 1569 override 1570 protected void toStringAppendFields(StringBuilder builder) { 1571 super.toStringAppendFields(builder); 1572 builder.append(", maxIdlePerKey="); 1573 builder.append(maxIdlePerKey); 1574 builder.append(", minIdlePerKey="); 1575 builder.append(minIdlePerKey); 1576 builder.append(", maxTotalPerKey="); 1577 builder.append(maxTotalPerKey); 1578 builder.append(", factory="); 1579 builder.append(factory); 1580 builder.append(", fairness="); 1581 builder.append(fairness); 1582 builder.append(", poolMap="); 1583 builder.append(poolMap); 1584 builder.append(", poolKeyList="); 1585 builder.append(poolKeyList); 1586 builder.append(", keyLock="); 1587 builder.append(keyLock); 1588 builder.append(", numTotal="); 1589 builder.append(numTotal); 1590 builder.append(", evictionKeyIterator="); 1591 builder.append(evictionKeyIterator); 1592 builder.append(", evictionKey="); 1593 builder.append(evictionKey); 1594 } 1595 }