| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.pool2.impl; |
| |
| import java.lang.ref.Reference; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.SoftReference; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.NoSuchElementException; |
| |
| import org.apache.commons.pool2.BaseObjectPool; |
| import org.apache.commons.pool2.ObjectPool; |
| import org.apache.commons.pool2.PoolUtils; |
| import org.apache.commons.pool2.PooledObjectFactory; |
| |
| /** |
| * A {@link java.lang.ref.SoftReference SoftReference} based {@link ObjectPool}. |
| * <p> |
| * This class is intended to be thread-safe. |
| * </p> |
| * |
| * @param <T> |
| * Type of element pooled in this pool. |
| * |
| * @since 2.0 |
| */ |
| public class SoftReferenceObjectPool<T> extends BaseObjectPool<T> { |
| |
| /** Factory to source pooled objects */ |
| private final PooledObjectFactory<T> factory; |
| |
| /** |
| * Queue of broken references that might be able to be removed from |
| * {@code _pool}. This is used to help {@link #getNumIdle()} be more |
| * accurate with minimal performance overhead. |
| */ |
| private final ReferenceQueue<T> refQueue = new ReferenceQueue<>(); |
| |
| /** Count of instances that have been checkout out to pool clients */ |
| private int numActive = 0; // @GuardedBy("this") |
| |
| /** Total number of instances that have been destroyed */ |
| private long destroyCount = 0; // @GuardedBy("this") |
| |
| |
| /** Total number of instances that have been created */ |
| private long createCount = 0; // @GuardedBy("this") |
| |
| /** Idle references - waiting to be borrowed */ |
| private final LinkedBlockingDeque<PooledSoftReference<T>> idleReferences = |
| new LinkedBlockingDeque<>(); |
| |
| /** All references - checked out or waiting to be borrowed. */ |
| private final ArrayList<PooledSoftReference<T>> allReferences = |
| new ArrayList<>(); |
| |
| /** |
| * Create a {@code SoftReferenceObjectPool} with the specified factory. |
| * |
| * @param factory object factory to use. |
| */ |
| public SoftReferenceObjectPool(final PooledObjectFactory<T> factory) { |
| this.factory = factory; |
| } |
| |
| /** |
| * Borrows an object from the pool. If there are no idle instances available |
| * in the pool, the configured factory's |
| * {@link PooledObjectFactory#makeObject()} method is invoked to create a |
| * new instance. |
| * <p> |
| * All instances are {@link PooledObjectFactory#activateObject( |
| * org.apache.commons.pool2.PooledObject) activated} |
| * and {@link PooledObjectFactory#validateObject( |
| * org.apache.commons.pool2.PooledObject) |
| * validated} before being returned by this method. If validation fails or |
| * an exception occurs activating or validating an idle instance, the |
| * failing instance is {@link PooledObjectFactory#destroyObject( |
| * org.apache.commons.pool2.PooledObject) |
| * destroyed} and another instance is retrieved from the pool, validated and |
| * activated. This process continues until either the pool is empty or an |
| * instance passes validation. If the pool is empty on activation or it does |
| * not contain any valid instances, the factory's {@code makeObject} |
| * method is used to create a new instance. If the created instance either |
| * raises an exception on activation or fails validation, |
| * {@code NoSuchElementException} is thrown. Exceptions thrown by |
| * {@code MakeObject} are propagated to the caller; but other than |
| * {@code ThreadDeath} or {@code VirtualMachineError}, exceptions |
| * generated by activation, validation or destroy methods are swallowed |
| * silently. |
| * |
| * @throws NoSuchElementException |
| * if a valid object cannot be provided |
| * @throws IllegalStateException |
| * if invoked on a {@link #close() closed} pool |
| * @throws Exception |
| * if an exception occurs creating a new instance |
| * @return a valid, activated object instance |
| */ |
| @SuppressWarnings("null") // ref cannot be null |
| @Override |
| public synchronized T borrowObject() throws Exception { |
| assertOpen(); |
| T obj = null; |
| boolean newlyCreated = false; |
| PooledSoftReference<T> ref = null; |
| while (null == obj) { |
| if (idleReferences.isEmpty()) { |
| if (null == factory) { |
| throw new NoSuchElementException(); |
| } |
| newlyCreated = true; |
| obj = factory.makeObject().getObject(); |
| createCount++; |
| // Do not register with the queue |
| ref = new PooledSoftReference<>(new SoftReference<>(obj)); |
| allReferences.add(ref); |
| } else { |
| ref = idleReferences.pollFirst(); |
| obj = ref.getObject(); |
| // Clear the reference so it will not be queued, but replace with a |
| // a new, non-registered reference so we can still track this object |
| // in allReferences |
| ref.getReference().clear(); |
| ref.setReference(new SoftReference<>(obj)); |
| } |
| if (null != factory && null != obj) { |
| try { |
| factory.activateObject(ref); |
| if (!factory.validateObject(ref)) { |
| throw new Exception("ValidateObject failed"); |
| } |
| } catch (final Throwable t) { |
| PoolUtils.checkRethrow(t); |
| try { |
| destroy(ref); |
| } catch (final Throwable t2) { |
| PoolUtils.checkRethrow(t2); |
| // Swallowed |
| } finally { |
| obj = null; |
| } |
| if (newlyCreated) { |
| throw new NoSuchElementException( |
| "Could not create a validated object, cause: " + |
| t.getMessage()); |
| } |
| } |
| } |
| } |
| numActive++; |
| ref.allocate(); |
| return obj; |
| } |
| |
| /** |
| * Returns an instance to the pool after successful validation and |
| * passivation. The returning instance is destroyed if any of the following |
| * are true: |
| * <ul> |
| * <li>the pool is closed</li> |
| * <li>{@link PooledObjectFactory#validateObject( |
| * org.apache.commons.pool2.PooledObject) validation} fails |
| * </li> |
| * <li>{@link PooledObjectFactory#passivateObject( |
| * org.apache.commons.pool2.PooledObject) passivation} |
| * throws an exception</li> |
| * </ul> |
| * Exceptions passivating or destroying instances are silently swallowed. |
| * Exceptions validating instances are propagated to the client. |
| * |
| * @param obj |
| * instance to return to the pool |
| * @throws IllegalArgumentException |
| * if obj is not currently part of this pool |
| */ |
| @Override |
| public synchronized void returnObject(final T obj) throws Exception { |
| boolean success = !isClosed(); |
| final PooledSoftReference<T> ref = findReference(obj); |
| if (ref == null) { |
| throw new IllegalStateException( |
| "Returned object not currently part of this pool"); |
| } |
| if (factory != null) { |
| if (!factory.validateObject(ref)) { |
| success = false; |
| } else { |
| try { |
| factory.passivateObject(ref); |
| } catch (final Exception e) { |
| success = false; |
| } |
| } |
| } |
| |
| final boolean shouldDestroy = !success; |
| numActive--; |
| if (success) { |
| |
| // Deallocate and add to the idle instance pool |
| ref.deallocate(); |
| idleReferences.add(ref); |
| } |
| notifyAll(); // numActive has changed |
| |
| if (shouldDestroy && factory != null) { |
| try { |
| destroy(ref); |
| } catch (final Exception e) { |
| // ignored |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public synchronized void invalidateObject(final T obj) throws Exception { |
| final PooledSoftReference<T> ref = findReference(obj); |
| if (ref == null) { |
| throw new IllegalStateException( |
| "Object to invalidate is not currently part of this pool"); |
| } |
| if (factory != null) { |
| destroy(ref); |
| } |
| numActive--; |
| notifyAll(); // numActive has changed |
| } |
| |
| /** |
| * Creates an object, and places it into the pool. addObject() is useful for |
| * "pre-loading" a pool with idle objects. |
| * <p> |
| * Before being added to the pool, the newly created instance is |
| * {@link PooledObjectFactory#validateObject( |
| * org.apache.commons.pool2.PooledObject) validated} and |
| * {@link PooledObjectFactory#passivateObject( |
| * org.apache.commons.pool2.PooledObject) passivated}. If |
| * validation fails, the new instance is |
| * {@link PooledObjectFactory#destroyObject( |
| * org.apache.commons.pool2.PooledObject) destroyed}. Exceptions |
| * generated by the factory {@code makeObject} or |
| * {@code passivate} are propagated to the caller. Exceptions |
| * destroying instances are silently swallowed. |
| * |
| * @throws IllegalStateException |
| * if invoked on a {@link #close() closed} pool |
| * @throws Exception |
| * when the {@link #getFactory() factory} has a problem creating |
| * or passivating an object. |
| */ |
| @Override |
| public synchronized void addObject() throws Exception { |
| assertOpen(); |
| if (factory == null) { |
| throw new IllegalStateException( |
| "Cannot add objects without a factory."); |
| } |
| final T obj = factory.makeObject().getObject(); |
| createCount++; |
| // Create and register with the queue |
| final PooledSoftReference<T> ref = new PooledSoftReference<>( |
| new SoftReference<>(obj, refQueue)); |
| allReferences.add(ref); |
| |
| boolean success = true; |
| if (!factory.validateObject(ref)) { |
| success = false; |
| } else { |
| factory.passivateObject(ref); |
| } |
| |
| final boolean shouldDestroy = !success; |
| if (success) { |
| idleReferences.add(ref); |
| notifyAll(); // numActive has changed |
| } |
| |
| if (shouldDestroy) { |
| try { |
| destroy(ref); |
| } catch (final Exception e) { |
| // ignored |
| } |
| } |
| } |
| |
| /** |
| * Returns an approximation not less than the of the number of idle |
| * instances in the pool. |
| * |
| * @return estimated number of idle instances in the pool |
| */ |
| @Override |
| public synchronized int getNumIdle() { |
| pruneClearedReferences(); |
| return idleReferences.size(); |
| } |
| |
| /** |
| * Returns the number of instances currently borrowed from this pool. |
| * |
| * @return the number of instances currently borrowed from this pool |
| */ |
| @Override |
| public synchronized int getNumActive() { |
| return numActive; |
| } |
| |
| /** |
| * Clears any objects sitting idle in the pool. |
| */ |
| @Override |
| public synchronized void clear() { |
| if (null != factory) { |
| final Iterator<PooledSoftReference<T>> iter = idleReferences.iterator(); |
| while (iter.hasNext()) { |
| try { |
| final PooledSoftReference<T> ref = iter.next(); |
| if (null != ref.getObject()) { |
| factory.destroyObject(ref); |
| } |
| } catch (final Exception e) { |
| // ignore error, keep destroying the rest |
| } |
| } |
| } |
| idleReferences.clear(); |
| pruneClearedReferences(); |
| } |
| |
| /** |
| * Closes this pool, and frees any resources associated with it. Invokes |
| * {@link #clear()} to destroy and remove instances in the pool. |
| * <p> |
| * Calling {@link #addObject} or {@link #borrowObject} after invoking this |
| * method on a pool will cause them to throw an |
| * {@link IllegalStateException}. |
| */ |
| @Override |
| public void close() { |
| super.close(); |
| clear(); |
| } |
| |
| /** |
| * Returns the {@link PooledObjectFactory} used by this pool to create and |
| * manage object instances. |
| * |
| * @return the factory |
| */ |
| public synchronized PooledObjectFactory<T> getFactory() { |
| return factory; |
| } |
| |
| /** |
| * If any idle objects were garbage collected, remove their |
| * {@link Reference} wrappers from the idle object pool. |
| */ |
| private void pruneClearedReferences() { |
| // Remove wrappers for enqueued references from idle and allReferences lists |
| removeClearedReferences(idleReferences.iterator()); |
| removeClearedReferences(allReferences.iterator()); |
| while (refQueue.poll() != null) { |
| // empty |
| } |
| } |
| |
| /** |
| * Finds the PooledSoftReference in allReferences that points to obj. |
| * |
| * @param obj returning object |
| * @return PooledSoftReference wrapping a soft reference to obj |
| */ |
| private PooledSoftReference<T> findReference(final T obj) { |
| final Iterator<PooledSoftReference<T>> iterator = allReferences.iterator(); |
| while (iterator.hasNext()) { |
| final PooledSoftReference<T> reference = iterator.next(); |
| if (reference.getObject() != null && reference.getObject().equals(obj)) { |
| return reference; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Destroys a {@code PooledSoftReference} and removes it from the idle and all |
| * references pools. |
| * |
| * @param toDestroy PooledSoftReference to destroy |
| * |
| * @throws Exception If an error occurs while trying to destroy the object |
| */ |
| private void destroy(final PooledSoftReference<T> toDestroy) throws Exception { |
| toDestroy.invalidate(); |
| idleReferences.remove(toDestroy); |
| allReferences.remove(toDestroy); |
| try { |
| factory.destroyObject(toDestroy); |
| } finally { |
| destroyCount++; |
| toDestroy.getReference().clear(); |
| } |
| } |
| |
| /** |
| * Clears cleared references from iterator's collection |
| * @param iterator iterator over idle/allReferences |
| */ |
| private void removeClearedReferences(final Iterator<PooledSoftReference<T>> iterator) { |
| PooledSoftReference<T> ref; |
| while (iterator.hasNext()) { |
| ref = iterator.next(); |
| if (ref.getReference() == null || ref.getReference().isEnqueued()) { |
| iterator.remove(); |
| } |
| } |
| } |
| |
| @Override |
| protected void toStringAppendFields(final StringBuilder builder) { |
| super.toStringAppendFields(builder); |
| builder.append(", factory="); |
| builder.append(factory); |
| builder.append(", refQueue="); |
| builder.append(refQueue); |
| builder.append(", numActive="); |
| builder.append(numActive); |
| builder.append(", destroyCount="); |
| builder.append(destroyCount); |
| builder.append(", createCount="); |
| builder.append(createCount); |
| builder.append(", idleReferences="); |
| builder.append(idleReferences); |
| builder.append(", allReferences="); |
| builder.append(allReferences); |
| } |
| } |