    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 e) {
    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 e) {
    242                    // ignore error, 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) {
    355            // empty
    356        }
    357    }
    358
    359    /**
    360     * Clears cleared references from iterator's collection
    361     * @param iterator iterator over idle/allReferences
    362     */
    363    private void removeClearedReferences(final Iterator<PooledSoftReference<T>> iterator) {
    364        PooledSoftReference<T> ref;
    365        while (iterator.hasNext()) {
    366            ref = iterator.next();
    367            if (ref.getReference() == null || ref.getReference().isEnqueued()) {
    368                iterator.remove();
    369            }
    370        }
    371    }
    372
    373    /**
    374     * Returns an instance to the pool after successful validation and
    375     * passivation. The returning instance is destroyed if any of the following
    376     * are true:
    377     * <ul>
    378     * <li>the pool is closed</li>
    379     * <li>{@link PooledObjectFactory#validateObject(
    380     * org.apache.commons.pool2.PooledObject) validation} fails
    381     * </li>
    382     * <li>{@link PooledObjectFactory#passivateObject(
    383     * org.apache.commons.pool2.PooledObject) passivation}
    384     * throws an exception</li>
    385     * </ul>
    386     * Exceptions passivating or destroying instances are silently swallowed.
    387     * Exceptions validating instances are propagated to the client.
    388     *
    389     * @param obj
    390     *            instance to return to the pool
    391     * @throws IllegalArgumentException
    392     *            if obj is not currently part of this pool
    393     */
    394    @Override
    395    public synchronized void returnObject(final T obj) throws Exception {
    396        boolean success = !isClosed();
    397        final PooledSoftReference<T> ref = findReference(obj);
    398        if (ref == null) {
    399            throw new IllegalStateException(
    400                "Returned object not currently part of this pool");
    401        }
    402        if (factory != null) {
    403            if (!factory.validateObject(ref)) {
    404                success = false;
    405            } else {
    406                try {
    407                    factory.passivateObject(ref);
    408                } catch (final Exception e) {
    409                    success = false;
    410                }
    411            }
    412        }
    413
    414        final boolean shouldDestroy = !success;
    415        numActive--;
    416        if (success) {
    417
    418            // Deallocate and add to the idle instance pool
    419            ref.deallocate();
    420            idleReferences.add(ref);
    421        }
    422        notifyAll(); // numActive has changed
    423
    424        if (shouldDestroy && factory != null) {
    425            try {
    426                destroy(ref);
    427            } catch (final Exception e) {
    428                // ignored
    429            }
    430        }
    431    }
    432
    433    @Override
    434    protected void toStringAppendFields(final StringBuilder builder) {
    435        super.toStringAppendFields(builder);
    436        builder.append(", factory=");
    437        builder.append(factory);
    438        builder.append(", refQueue=");
    439        builder.append(refQueue);
    440        builder.append(", numActive=");
    441        builder.append(numActive);
    442        builder.append(", destroyCount=");
    443        builder.append(destroyCount);
    444        builder.append(", createCount=");
    445        builder.append(createCount);
    446        builder.append(", idleReferences=");
    447        builder.append(idleReferences);
    448        builder.append(", allReferences=");
    449        builder.append(allReferences);
    450    }
    451}