    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 *      https://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;
    026import java.util.concurrent.BlockingDeque;
    027
    028import org.apache.commons.pool2.BaseObjectPool;
    029import org.apache.commons.pool2.ObjectPool;
    030import org.apache.commons.pool2.PoolUtils;
    031import org.apache.commons.pool2.PooledObjectFactory;
    032
    033/**
    034 * A {@link java.lang.ref.SoftReference SoftReference} based {@link ObjectPool}.
    035 * <p>
    036 * This class is intended to be thread-safe.
    037 * </p>
    038 *
    039 * @param <T>
    040 *            Type of element pooled in this pool.
    041 *
    042 * @since 2.0
    043 */
    044public class SoftReferenceObjectPool<T> extends BaseObjectPool<T> {
    045
    046    /** Factory to source pooled objects */
    047    private final PooledObjectFactory<T> factory;
    048
    049    /**
    050     * Queue of broken references that might be able to be removed from
    051     * {@code _pool}. This is used to help {@link #getNumIdle()} be more
    052     * accurate with minimal performance overhead.
    053     */
    054    private final ReferenceQueue<T> refQueue = new ReferenceQueue<>();
    055
    056    /** Count of instances that have been checkout out to pool clients */
    057    private int numActive; // @GuardedBy("this")
    058
    059    /** Total number of instances that have been destroyed */
    060    private long destroyCount; // @GuardedBy("this")
    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 BlockingDeque<PooledSoftReference<T>> idleReferences = 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 {@link 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}