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