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