001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.pool2.impl; 018 019import java.lang.ref.Reference; 020import java.lang.ref.ReferenceQueue; 021import java.lang.ref.SoftReference; 022import java.util.ArrayList; 023import java.util.Iterator; 024import java.util.NoSuchElementException; 025import java.util.Optional; 026 027import org.apache.commons.pool2.BaseObjectPool; 028import org.apache.commons.pool2.ObjectPool; 029import org.apache.commons.pool2.PoolUtils; 030import org.apache.commons.pool2.PooledObjectFactory; 031 032/** 033 * A {@link java.lang.ref.SoftReference SoftReference} based {@link ObjectPool}. 034 * <p> 035 * This class is intended to be thread-safe. 036 * </p> 037 * 038 * @param <T> 039 * Type of element pooled in this pool. 040 * 041 * @since 2.0 042 */ 043public class SoftReferenceObjectPool<T> extends BaseObjectPool<T> { 044 045 /** Factory to source pooled objects */ 046 private final PooledObjectFactory<T> factory; 047 048 /** 049 * Queue of broken references that might be able to be removed from 050 * {@code _pool}. This is used to help {@link #getNumIdle()} be more 051 * accurate with minimal performance overhead. 052 */ 053 private final ReferenceQueue<T> refQueue = new ReferenceQueue<>(); 054 055 /** Count of instances that have been checkout out to pool clients */ 056 private int numActive; // @GuardedBy("this") 057 058 /** Total number of instances that have been destroyed */ 059 private long destroyCount; // @GuardedBy("this") 060 061 062 /** Total number of instances that have been created */ 063 private long createCount; // @GuardedBy("this") 064 065 /** Idle references - waiting to be borrowed */ 066 private final LinkedBlockingDeque<PooledSoftReference<T>> idleReferences = 067 new LinkedBlockingDeque<>(); 068 069 /** All references - checked out or waiting to be borrowed. */ 070 private final ArrayList<PooledSoftReference<T>> allReferences = 071 new ArrayList<>(); 072 073 /** 074 * Constructs a {@code SoftReferenceObjectPool} with the specified factory. 075 * 076 * @param factory object factory to use. 077 */ 078 public SoftReferenceObjectPool(final PooledObjectFactory<T> factory) { 079 this.factory = factory; 080 } 081 082 /** 083 * Creates an object, and places it into the pool. addObject() is useful for 084 * "pre-loading" a pool with idle objects. 085 * <p> 086 * Before being added to the pool, the newly created instance is 087 * {@link PooledObjectFactory#validateObject( 088 * org.apache.commons.pool2.PooledObject) validated} and 089 * {@link PooledObjectFactory#passivateObject( 090 * org.apache.commons.pool2.PooledObject) passivated}. If 091 * validation fails, the new instance is 092 * {@link PooledObjectFactory#destroyObject( 093 * org.apache.commons.pool2.PooledObject) destroyed}. Exceptions 094 * generated by the factory {@code makeObject} or 095 * {@code passivate} are propagated to the caller. Exceptions 096 * destroying instances are silently swallowed. 097 * </p> 098 * 099 * @throws IllegalStateException 100 * if invoked on a {@link #close() closed} pool 101 * @throws Exception 102 * when the {@link #getFactory() factory} has a problem creating 103 * or passivating an object. 104 */ 105 @Override 106 public synchronized void addObject() throws Exception { 107 assertOpen(); 108 if (factory == null) { 109 throw new IllegalStateException( 110 "Cannot add objects without a factory."); 111 } 112 final T obj = factory.makeObject().getObject(); 113 createCount++; 114 // Create and register with the queue 115 final PooledSoftReference<T> ref = new PooledSoftReference<>( 116 new SoftReference<>(obj, refQueue)); 117 allReferences.add(ref); 118 119 boolean success = true; 120 if (!factory.validateObject(ref)) { 121 success = false; 122 } else { 123 factory.passivateObject(ref); 124 } 125 126 final boolean shouldDestroy = !success; 127 if (success) { 128 idleReferences.add(ref); 129 notifyAll(); // numActive has changed 130 } 131 132 if (shouldDestroy) { 133 try { 134 destroy(ref); 135 } catch (final Exception ignored) { 136 // ignored 137 } 138 } 139 } 140 141 /** 142 * Borrows an object from the pool. If there are no idle instances available 143 * in the pool, the configured factory's 144 * {@link PooledObjectFactory#makeObject()} method is invoked to create a 145 * new instance. 146 * <p> 147 * All instances are {@link PooledObjectFactory#activateObject( 148 * org.apache.commons.pool2.PooledObject) activated} 149 * and {@link PooledObjectFactory#validateObject( 150 * org.apache.commons.pool2.PooledObject) 151 * validated} before being returned by this method. If validation fails or 152 * an exception occurs activating or validating an idle instance, the 153 * failing instance is {@link PooledObjectFactory#destroyObject( 154 * org.apache.commons.pool2.PooledObject) 155 * destroyed} and another instance is retrieved from the pool, validated and 156 * activated. This process continues until either the pool is empty or an 157 * instance passes validation. If the pool is empty on activation or it does 158 * not contain any valid instances, the factory's {@code makeObject} 159 * method is used to create a new instance. If the created instance either 160 * raises an exception on activation or fails validation, 161 * {@code NoSuchElementException} is thrown. Exceptions thrown by 162 * {@code MakeObject} are propagated to the caller; but other than 163 * {@code ThreadDeath} or {@code VirtualMachineError}, exceptions 164 * generated by activation, validation or destroy methods are swallowed 165 * silently. 166 * </p> 167 * 168 * @throws NoSuchElementException 169 * if a valid object cannot be provided 170 * @throws IllegalStateException 171 * if invoked on a {@link #close() closed} pool 172 * @throws Exception 173 * if an exception occurs creating a new instance 174 * @return a valid, activated object instance 175 */ 176 @SuppressWarnings("null") // ref cannot be null 177 @Override 178 public synchronized T borrowObject() throws Exception { 179 assertOpen(); 180 T obj = null; 181 boolean newlyCreated = false; 182 PooledSoftReference<T> ref = null; 183 while (null == obj) { 184 if (idleReferences.isEmpty()) { 185 if (null == factory) { 186 throw new NoSuchElementException(); 187 } 188 newlyCreated = true; 189 obj = factory.makeObject().getObject(); 190 createCount++; 191 // Do not register with the queue 192 ref = new PooledSoftReference<>(new SoftReference<>(obj)); 193 allReferences.add(ref); 194 } else { 195 ref = idleReferences.pollFirst(); 196 obj = ref.getObject(); 197 // Clear the reference so it will not be queued, but replace with a 198 // a new, non-registered reference so we can still track this object 199 // in allReferences 200 ref.getReference().clear(); 201 ref.setReference(new SoftReference<>(obj)); 202 } 203 if (null != factory && null != obj) { 204 try { 205 factory.activateObject(ref); 206 if (!factory.validateObject(ref)) { 207 throw new Exception("ValidateObject failed"); 208 } 209 } catch (final Throwable t) { 210 PoolUtils.checkRethrow(t); 211 try { 212 destroy(ref); 213 } catch (final Throwable t2) { 214 PoolUtils.checkRethrow(t2); 215 // Swallowed 216 } finally { 217 obj = null; 218 } 219 if (newlyCreated) { 220 throw new NoSuchElementException("Could not create a validated object, cause: " + t); 221 } 222 } 223 } 224 } 225 numActive++; 226 ref.allocate(); 227 return obj; 228 } 229 230 /** 231 * Clears any objects sitting idle in the pool. 232 */ 233 @Override 234 public synchronized void clear() { 235 if (null != factory) { 236 idleReferences.forEach(ref -> { 237 try { 238 if (null != ref.getObject()) { 239 factory.destroyObject(ref); 240 } 241 } catch (final Exception ignored) { 242 // ignored, keep destroying the rest 243 } 244 }); 245 } 246 idleReferences.clear(); 247 pruneClearedReferences(); 248 } 249 250 /** 251 * Closes this pool, and frees any resources associated with it. Invokes 252 * {@link #clear()} to destroy and remove instances in the pool. 253 * <p> 254 * Calling {@link #addObject} or {@link #borrowObject} after invoking this 255 * method on a pool will cause them to throw an 256 * {@link IllegalStateException}. 257 * </p> 258 */ 259 @Override 260 public void close() { 261 super.close(); 262 clear(); 263 } 264 265 /** 266 * Destroys a {@code PooledSoftReference} and removes it from the idle and all 267 * references pools. 268 * 269 * @param toDestroy PooledSoftReference to destroy 270 * 271 * @throws Exception If an error occurs while trying to destroy the object 272 */ 273 private void destroy(final PooledSoftReference<T> toDestroy) throws Exception { 274 toDestroy.invalidate(); 275 idleReferences.remove(toDestroy); 276 allReferences.remove(toDestroy); 277 try { 278 factory.destroyObject(toDestroy); 279 } finally { 280 destroyCount++; 281 toDestroy.getReference().clear(); 282 } 283 } 284 285 /** 286 * Finds the PooledSoftReference in allReferences that points to obj. 287 * 288 * @param obj returning object 289 * @return PooledSoftReference wrapping a soft reference to obj 290 */ 291 private PooledSoftReference<T> findReference(final T obj) { 292 final Optional<PooledSoftReference<T>> first = allReferences.stream() 293 .filter(reference -> reference.getObject() != null && reference.getObject().equals(obj)).findFirst(); 294 return first.orElse(null); 295 } 296 297 /** 298 * Gets the {@link PooledObjectFactory} used by this pool to create and 299 * manage object instances. 300 * 301 * @return the factory 302 */ 303 public synchronized PooledObjectFactory<T> getFactory() { 304 return factory; 305 } 306 307 /** 308 * Gets the number of instances currently borrowed from this pool. 309 * 310 * @return the number of instances currently borrowed from this pool 311 */ 312 @Override 313 public synchronized int getNumActive() { 314 return numActive; 315 } 316 317 /** 318 * Gets an approximation not less than the of the number of idle 319 * instances in the pool. 320 * 321 * @return estimated number of idle instances in the pool 322 */ 323 @Override 324 public synchronized int getNumIdle() { 325 pruneClearedReferences(); 326 return idleReferences.size(); 327 } 328 329 /** 330 * {@inheritDoc} 331 */ 332 @Override 333 public synchronized void invalidateObject(final T obj) throws Exception { 334 final PooledSoftReference<T> ref = findReference(obj); 335 if (ref == null) { 336 throw new IllegalStateException( 337 "Object to invalidate is not currently part of this pool"); 338 } 339 if (factory != null) { 340 destroy(ref); 341 } 342 numActive--; 343 notifyAll(); // numActive has changed 344 } 345 346 /** 347 * If any idle objects were garbage collected, remove their 348 * {@link Reference} wrappers from the idle object pool. 349 */ 350 private void pruneClearedReferences() { 351 // Remove wrappers for enqueued references from idle and allReferences lists 352 removeClearedReferences(idleReferences.iterator()); 353 removeClearedReferences(allReferences.iterator()); 354 while (refQueue.poll() != null) { // NOPMD 355 } 356 } 357 358 /** 359 * Clears cleared references from iterator's collection 360 * @param iterator iterator over idle/allReferences 361 */ 362 private void removeClearedReferences(final Iterator<PooledSoftReference<T>> iterator) { 363 PooledSoftReference<T> ref; 364 while (iterator.hasNext()) { 365 ref = iterator.next(); 366 if (ref.getReference() == null || ref.getReference().isEnqueued()) { 367 iterator.remove(); 368 } 369 } 370 } 371 372 /** 373 * Returns an instance to the pool after successful validation and 374 * passivation. The returning instance is destroyed if any of the following 375 * are true: 376 * <ul> 377 * <li>the pool is closed</li> 378 * <li>{@link PooledObjectFactory#validateObject( 379 * org.apache.commons.pool2.PooledObject) validation} fails 380 * </li> 381 * <li>{@link PooledObjectFactory#passivateObject( 382 * org.apache.commons.pool2.PooledObject) passivation} 383 * throws an exception</li> 384 * </ul> 385 * Exceptions passivating or destroying instances are silently swallowed. 386 * Exceptions validating instances are propagated to the client. 387 * 388 * @param obj 389 * instance to return to the pool 390 * @throws IllegalArgumentException 391 * if obj is not currently part of this pool 392 */ 393 @Override 394 public synchronized void returnObject(final T obj) throws Exception { 395 boolean success = !isClosed(); 396 final PooledSoftReference<T> ref = findReference(obj); 397 if (ref == null) { 398 throw new IllegalStateException( 399 "Returned object not currently part of this pool"); 400 } 401 if (factory != null) { 402 if (!factory.validateObject(ref)) { 403 success = false; 404 } else { 405 try { 406 factory.passivateObject(ref); 407 } catch (final Exception e) { 408 success = false; 409 } 410 } 411 } 412 413 final boolean shouldDestroy = !success; 414 numActive--; 415 if (success) { 416 417 // Deallocate and add to the idle instance pool 418 ref.deallocate(); 419 idleReferences.add(ref); 420 } 421 notifyAll(); // numActive has changed 422 423 if (shouldDestroy && factory != null) { 424 try { 425 destroy(ref); 426 } catch (final Exception ignored) { 427 // ignored 428 } 429 } 430 } 431 432 @Override 433 protected void toStringAppendFields(final StringBuilder builder) { 434 super.toStringAppendFields(builder); 435 builder.append(", factory="); 436 builder.append(factory); 437 builder.append(", refQueue="); 438 builder.append(refQueue); 439 builder.append(", numActive="); 440 builder.append(numActive); 441 builder.append(", destroyCount="); 442 builder.append(destroyCount); 443 builder.append(", createCount="); 444 builder.append(createCount); 445 builder.append(", idleReferences="); 446 builder.append(idleReferences); 447 builder.append(", allReferences="); 448 builder.append(allReferences); 449 } 450}