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; 025 026import org.apache.commons.pool2.BaseObjectPool; 027import org.apache.commons.pool2.ObjectPool; 028import org.apache.commons.pool2.PoolUtils; 029import org.apache.commons.pool2.PooledObjectFactory; 030 031/** 032 * A {@link java.lang.ref.SoftReference SoftReference} based {@link ObjectPool}. 033 * <p> 034 * This class is intended to be thread-safe. 035 * </p> 036 * 037 * @param <T> 038 * Type of element pooled in this pool. 039 * 040 * @since 2.0 041 */ 042public class SoftReferenceObjectPool<T> extends BaseObjectPool<T> { 043 044 /** Factory to source pooled objects */ 045 private final PooledObjectFactory<T> factory; 046 047 /** 048 * Queue of broken references that might be able to be removed from 049 * {@code _pool}. This is used to help {@link #getNumIdle()} be more 050 * accurate with minimal performance overhead. 051 */ 052 private final ReferenceQueue<T> refQueue = new ReferenceQueue<>(); 053 054 /** Count of instances that have been checkout out to pool clients */ 055 private int numActive; // @GuardedBy("this") 056 057 /** Total number of instances that have been destroyed */ 058 private long destroyCount; // @GuardedBy("this") 059 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 * Create 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 * 097 * @throws IllegalStateException 098 * if invoked on a {@link #close() closed} pool 099 * @throws Exception 100 * when the {@link #getFactory() factory} has a problem creating 101 * or passivating an object. 102 */ 103 @Override 104 public synchronized void addObject() throws Exception { 105 assertOpen(); 106 if (factory == null) { 107 throw new IllegalStateException( 108 "Cannot add objects without a factory."); 109 } 110 final T obj = factory.makeObject().getObject(); 111 createCount++; 112 // Create and register with the queue 113 final PooledSoftReference<T> ref = new PooledSoftReference<>( 114 new SoftReference<>(obj, refQueue)); 115 allReferences.add(ref); 116 117 boolean success = true; 118 if (!factory.validateObject(ref)) { 119 success = false; 120 } else { 121 factory.passivateObject(ref); 122 } 123 124 final boolean shouldDestroy = !success; 125 if (success) { 126 idleReferences.add(ref); 127 notifyAll(); // numActive has changed 128 } 129 130 if (shouldDestroy) { 131 try { 132 destroy(ref); 133 } catch (final Exception e) { 134 // ignored 135 } 136 } 137 } 138 139 /** 140 * Borrows an object from the pool. If there are no idle instances available 141 * in the pool, the configured factory's 142 * {@link PooledObjectFactory#makeObject()} method is invoked to create a 143 * new instance. 144 * <p> 145 * All instances are {@link PooledObjectFactory#activateObject( 146 * org.apache.commons.pool2.PooledObject) activated} 147 * and {@link PooledObjectFactory#validateObject( 148 * org.apache.commons.pool2.PooledObject) 149 * validated} before being returned by this method. If validation fails or 150 * an exception occurs activating or validating an idle instance, the 151 * failing instance is {@link PooledObjectFactory#destroyObject( 152 * org.apache.commons.pool2.PooledObject) 153 * destroyed} and another instance is retrieved from the pool, validated and 154 * activated. This process continues until either the pool is empty or an 155 * instance passes validation. If the pool is empty on activation or it does 156 * not contain any valid instances, the factory's {@code makeObject} 157 * method is used to create a new instance. If the created instance either 158 * raises an exception on activation or fails validation, 159 * {@code NoSuchElementException} is thrown. Exceptions thrown by 160 * {@code MakeObject} are propagated to the caller; but other than 161 * {@code ThreadDeath} or {@code VirtualMachineError}, exceptions 162 * generated by activation, validation or destroy methods are swallowed 163 * silently. 164 * 165 * @throws NoSuchElementException 166 * if a valid object cannot be provided 167 * @throws IllegalStateException 168 * if invoked on a {@link #close() closed} pool 169 * @throws Exception 170 * if an exception occurs creating a new instance 171 * @return a valid, activated object instance 172 */ 173 @SuppressWarnings("null") // ref cannot be null 174 @Override 175 public synchronized T borrowObject() throws Exception { 176 assertOpen(); 177 T obj = null; 178 boolean newlyCreated = false; 179 PooledSoftReference<T> ref = null; 180 while (null == obj) { 181 if (idleReferences.isEmpty()) { 182 if (null == factory) { 183 throw new NoSuchElementException(); 184 } 185 newlyCreated = true; 186 obj = factory.makeObject().getObject(); 187 createCount++; 188 // Do not register with the queue 189 ref = new PooledSoftReference<>(new SoftReference<>(obj)); 190 allReferences.add(ref); 191 } else { 192 ref = idleReferences.pollFirst(); 193 obj = ref.getObject(); 194 // Clear the reference so it will not be queued, but replace with a 195 // a new, non-registered reference so we can still track this object 196 // in allReferences 197 ref.getReference().clear(); 198 ref.setReference(new SoftReference<>(obj)); 199 } 200 if (null != factory && null != obj) { 201 try { 202 factory.activateObject(ref); 203 if (!factory.validateObject(ref)) { 204 throw new Exception("ValidateObject failed"); 205 } 206 } catch (final Throwable t) { 207 PoolUtils.checkRethrow(t); 208 try { 209 destroy(ref); 210 } catch (final Throwable t2) { 211 PoolUtils.checkRethrow(t2); 212 // Swallowed 213 } finally { 214 obj = null; 215 } 216 if (newlyCreated) { 217 throw new NoSuchElementException( 218 "Could not create a validated object, cause: " + 219 t.getMessage()); 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 final Iterator<PooledSoftReference<T>> iter = idleReferences.iterator(); 236 while (iter.hasNext()) { 237 try { 238 final PooledSoftReference<T> ref = iter.next(); 239 if (null != ref.getObject()) { 240 factory.destroyObject(ref); 241 } 242 } catch (final Exception e) { 243 // ignore error, keep destroying the rest 244 } 245 } 246 } 247 idleReferences.clear(); 248 pruneClearedReferences(); 249 } 250 251 /** 252 * Closes this pool, and frees any resources associated with it. Invokes 253 * {@link #clear()} to destroy and remove instances in the pool. 254 * <p> 255 * Calling {@link #addObject} or {@link #borrowObject} after invoking this 256 * method on a pool will cause them to throw an 257 * {@link IllegalStateException}. 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 Iterator<PooledSoftReference<T>> iterator = allReferences.iterator(); 293 while (iterator.hasNext()) { 294 final PooledSoftReference<T> reference = iterator.next(); 295 if (reference.getObject() != null && reference.getObject().equals(obj)) { 296 return reference; 297 } 298 } 299 return null; 300 } 301 302 /** 303 * Gets the {@link PooledObjectFactory} used by this pool to create and 304 * manage object instances. 305 * 306 * @return the factory 307 */ 308 public synchronized PooledObjectFactory<T> getFactory() { 309 return factory; 310 } 311 312 /** 313 * Gets the number of instances currently borrowed from this pool. 314 * 315 * @return the number of instances currently borrowed from this pool 316 */ 317 @Override 318 public synchronized int getNumActive() { 319 return numActive; 320 } 321 322 /** 323 * Gets an approximation not less than the of the number of idle 324 * instances in the pool. 325 * 326 * @return estimated number of idle instances in the pool 327 */ 328 @Override 329 public synchronized int getNumIdle() { 330 pruneClearedReferences(); 331 return idleReferences.size(); 332 } 333 334 /** 335 * {@inheritDoc} 336 */ 337 @Override 338 public synchronized void invalidateObject(final T obj) throws Exception { 339 final PooledSoftReference<T> ref = findReference(obj); 340 if (ref == null) { 341 throw new IllegalStateException( 342 "Object to invalidate is not currently part of this pool"); 343 } 344 if (factory != null) { 345 destroy(ref); 346 } 347 numActive--; 348 notifyAll(); // numActive has changed 349 } 350 351 /** 352 * If any idle objects were garbage collected, remove their 353 * {@link Reference} wrappers from the idle object pool. 354 */ 355 private void pruneClearedReferences() { 356 // Remove wrappers for enqueued references from idle and allReferences lists 357 removeClearedReferences(idleReferences.iterator()); 358 removeClearedReferences(allReferences.iterator()); 359 while (refQueue.poll() != null) { 360 // empty 361 } 362 } 363 364 /** 365 * Clears cleared references from iterator's collection 366 * @param iterator iterator over idle/allReferences 367 */ 368 private void removeClearedReferences(final Iterator<PooledSoftReference<T>> iterator) { 369 PooledSoftReference<T> ref; 370 while (iterator.hasNext()) { 371 ref = iterator.next(); 372 if (ref.getReference() == null || ref.getReference().isEnqueued()) { 373 iterator.remove(); 374 } 375 } 376 } 377 378 /** 379 * Returns an instance to the pool after successful validation and 380 * passivation. The returning instance is destroyed if any of the following 381 * are true: 382 * <ul> 383 * <li>the pool is closed</li> 384 * <li>{@link PooledObjectFactory#validateObject( 385 * org.apache.commons.pool2.PooledObject) validation} fails 386 * </li> 387 * <li>{@link PooledObjectFactory#passivateObject( 388 * org.apache.commons.pool2.PooledObject) passivation} 389 * throws an exception</li> 390 * </ul> 391 * Exceptions passivating or destroying instances are silently swallowed. 392 * Exceptions validating instances are propagated to the client. 393 * 394 * @param obj 395 * instance to return to the pool 396 * @throws IllegalArgumentException 397 * if obj is not currently part of this pool 398 */ 399 @Override 400 public synchronized void returnObject(final T obj) throws Exception { 401 boolean success = !isClosed(); 402 final PooledSoftReference<T> ref = findReference(obj); 403 if (ref == null) { 404 throw new IllegalStateException( 405 "Returned object not currently part of this pool"); 406 } 407 if (factory != null) { 408 if (!factory.validateObject(ref)) { 409 success = false; 410 } else { 411 try { 412 factory.passivateObject(ref); 413 } catch (final Exception e) { 414 success = false; 415 } 416 } 417 } 418 419 final boolean shouldDestroy = !success; 420 numActive--; 421 if (success) { 422 423 // Deallocate and add to the idle instance pool 424 ref.deallocate(); 425 idleReferences.add(ref); 426 } 427 notifyAll(); // numActive has changed 428 429 if (shouldDestroy && factory != null) { 430 try { 431 destroy(ref); 432 } catch (final Exception e) { 433 // ignored 434 } 435 } 436 } 437 438 @Override 439 protected void toStringAppendFields(final StringBuilder builder) { 440 super.toStringAppendFields(builder); 441 builder.append(", factory="); 442 builder.append(factory); 443 builder.append(", refQueue="); 444 builder.append(refQueue); 445 builder.append(", numActive="); 446 builder.append(numActive); 447 builder.append(", destroyCount="); 448 builder.append(destroyCount); 449 builder.append(", createCount="); 450 builder.append(createCount); 451 builder.append(", idleReferences="); 452 builder.append(idleReferences); 453 builder.append(", allReferences="); 454 builder.append(allReferences); 455 } 456}