SoftReferenceObjectPool.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.pool2.impl;

  18. import java.lang.ref.Reference;
  19. import java.lang.ref.ReferenceQueue;
  20. import java.lang.ref.SoftReference;
  21. import java.util.ArrayList;
  22. import java.util.Iterator;
  23. import java.util.NoSuchElementException;
  24. import java.util.Optional;

  25. import org.apache.commons.pool2.BaseObjectPool;
  26. import org.apache.commons.pool2.ObjectPool;
  27. import org.apache.commons.pool2.PoolUtils;
  28. import org.apache.commons.pool2.PooledObjectFactory;

  29. /**
  30.  * A {@link java.lang.ref.SoftReference SoftReference} based {@link ObjectPool}.
  31.  * <p>
  32.  * This class is intended to be thread-safe.
  33.  * </p>
  34.  *
  35.  * @param <T>
  36.  *            Type of element pooled in this pool.
  37.  *
  38.  * @since 2.0
  39.  */
  40. public class SoftReferenceObjectPool<T> extends BaseObjectPool<T> {

  41.     /** Factory to source pooled objects */
  42.     private final PooledObjectFactory<T> factory;

  43.     /**
  44.      * Queue of broken references that might be able to be removed from
  45.      * {@code _pool}. This is used to help {@link #getNumIdle()} be more
  46.      * accurate with minimal performance overhead.
  47.      */
  48.     private final ReferenceQueue<T> refQueue = new ReferenceQueue<>();

  49.     /** Count of instances that have been checkout out to pool clients */
  50.     private int numActive; // @GuardedBy("this")

  51.     /** Total number of instances that have been destroyed */
  52.     private long destroyCount; // @GuardedBy("this")

  53.     /** Total number of instances that have been created */
  54.     private long createCount; // @GuardedBy("this")

  55.     /** Idle references - waiting to be borrowed */
  56.     private final LinkedBlockingDeque<PooledSoftReference<T>> idleReferences =
  57.         new LinkedBlockingDeque<>();

  58.     /** All references - checked out or waiting to be borrowed. */
  59.     private final ArrayList<PooledSoftReference<T>> allReferences =
  60.         new ArrayList<>();

  61.     /**
  62.      * Constructs a {@code SoftReferenceObjectPool} with the specified factory.
  63.      *
  64.      * @param factory object factory to use.
  65.      */
  66.     public SoftReferenceObjectPool(final PooledObjectFactory<T> factory) {
  67.         this.factory = factory;
  68.     }

  69.     /**
  70.      * Creates an object, and places it into the pool. addObject() is useful for
  71.      * "pre-loading" a pool with idle objects.
  72.      * <p>
  73.      * Before being added to the pool, the newly created instance is
  74.      * {@link PooledObjectFactory#validateObject(
  75.      * org.apache.commons.pool2.PooledObject) validated} and
  76.      * {@link PooledObjectFactory#passivateObject(
  77.      * org.apache.commons.pool2.PooledObject) passivated}. If
  78.      * validation fails, the new instance is
  79.      * {@link PooledObjectFactory#destroyObject(
  80.      * org.apache.commons.pool2.PooledObject) destroyed}. Exceptions
  81.      * generated by the factory {@code makeObject} or
  82.      * {@code passivate} are propagated to the caller. Exceptions
  83.      * destroying instances are silently swallowed.
  84.      * </p>
  85.      *
  86.      * @throws IllegalStateException
  87.      *             if invoked on a {@link #close() closed} pool
  88.      * @throws Exception
  89.      *             when the {@link #getFactory() factory} has a problem creating
  90.      *             or passivating an object.
  91.      */
  92.     @Override
  93.     public synchronized void addObject() throws Exception {
  94.         assertOpen();
  95.         if (factory == null) {
  96.             throw new IllegalStateException(
  97.                     "Cannot add objects without a factory.");
  98.         }
  99.         final T obj = factory.makeObject().getObject();
  100.         createCount++;
  101.         // Create and register with the queue
  102.         final PooledSoftReference<T> ref = new PooledSoftReference<>(
  103.                 new SoftReference<>(obj, refQueue));
  104.         allReferences.add(ref);

  105.         boolean success = true;
  106.         if (!factory.validateObject(ref)) {
  107.             success = false;
  108.         } else {
  109.             factory.passivateObject(ref);
  110.         }

  111.         final boolean shouldDestroy = !success;
  112.         if (success) {
  113.             idleReferences.add(ref);
  114.             notifyAll(); // numActive has changed
  115.         }

  116.         if (shouldDestroy) {
  117.             try {
  118.                 destroy(ref);
  119.             } catch (final Exception ignored) {
  120.                 // ignored
  121.             }
  122.         }
  123.     }

  124.     /**
  125.      * Borrows an object from the pool. If there are no idle instances available
  126.      * in the pool, the configured factory's
  127.      * {@link PooledObjectFactory#makeObject()} method is invoked to create a
  128.      * new instance.
  129.      * <p>
  130.      * All instances are {@link PooledObjectFactory#activateObject(
  131.      * org.apache.commons.pool2.PooledObject) activated}
  132.      * and {@link PooledObjectFactory#validateObject(
  133.      * org.apache.commons.pool2.PooledObject)
  134.      * validated} before being returned by this method. If validation fails or
  135.      * an exception occurs activating or validating an idle instance, the
  136.      * failing instance is {@link PooledObjectFactory#destroyObject(
  137.      * org.apache.commons.pool2.PooledObject)
  138.      * destroyed} and another instance is retrieved from the pool, validated and
  139.      * activated. This process continues until either the pool is empty or an
  140.      * instance passes validation. If the pool is empty on activation or it does
  141.      * not contain any valid instances, the factory's {@code makeObject}
  142.      * method is used to create a new instance. If the created instance either
  143.      * raises an exception on activation or fails validation,
  144.      * {@code NoSuchElementException} is thrown. Exceptions thrown by
  145.      * {@code MakeObject} are propagated to the caller; but other than
  146.      * {@code ThreadDeath} or {@code VirtualMachineError}, exceptions
  147.      * generated by activation, validation or destroy methods are swallowed
  148.      * silently.
  149.      * </p>
  150.      *
  151.      * @throws NoSuchElementException
  152.      *             if a valid object cannot be provided
  153.      * @throws IllegalStateException
  154.      *             if invoked on a {@link #close() closed} pool
  155.      * @throws Exception
  156.      *             if an exception occurs creating a new instance
  157.      * @return a valid, activated object instance
  158.      */
  159.     @SuppressWarnings("null") // ref cannot be null
  160.     @Override
  161.     public synchronized T borrowObject() throws Exception {
  162.         assertOpen();
  163.         T obj = null;
  164.         boolean newlyCreated = false;
  165.         PooledSoftReference<T> ref = null;
  166.         while (null == obj) {
  167.             if (idleReferences.isEmpty()) {
  168.                 if (null == factory) {
  169.                     throw new NoSuchElementException();
  170.                 }
  171.                 newlyCreated = true;
  172.                 obj = factory.makeObject().getObject();
  173.                 createCount++;
  174.                 // Do not register with the queue
  175.                 ref = new PooledSoftReference<>(new SoftReference<>(obj));
  176.                 allReferences.add(ref);
  177.             } else {
  178.                 ref = idleReferences.pollFirst();
  179.                 obj = ref.getObject();
  180.                 // Clear the reference so it will not be queued, but replace with a
  181.                 // a new, non-registered reference so we can still track this object
  182.                 // in allReferences
  183.                 ref.getReference().clear();
  184.                 ref.setReference(new SoftReference<>(obj));
  185.             }
  186.             if (null != factory && null != obj) {
  187.                 try {
  188.                     factory.activateObject(ref);
  189.                     if (!factory.validateObject(ref)) {
  190.                         throw new Exception("ValidateObject failed");
  191.                     }
  192.                 } catch (final Throwable t) {
  193.                     PoolUtils.checkRethrow(t);
  194.                     try {
  195.                         destroy(ref);
  196.                     } catch (final Throwable t2) {
  197.                         PoolUtils.checkRethrow(t2);
  198.                         // Swallowed
  199.                     } finally {
  200.                         obj = null;
  201.                     }
  202.                     if (newlyCreated) {
  203.                         throw new NoSuchElementException("Could not create a validated object, cause: " + t);
  204.                     }
  205.                 }
  206.             }
  207.         }
  208.         numActive++;
  209.         ref.allocate();
  210.         return obj;
  211.     }

  212.     /**
  213.      * Clears any objects sitting idle in the pool.
  214.      */
  215.     @Override
  216.     public synchronized void clear() {
  217.         if (null != factory) {
  218.             idleReferences.forEach(ref -> {
  219.                 try {
  220.                     if (null != ref.getObject()) {
  221.                         factory.destroyObject(ref);
  222.                     }
  223.                 } catch (final Exception ignored) {
  224.                     // ignored, keep destroying the rest
  225.                 }
  226.             });
  227.         }
  228.         idleReferences.clear();
  229.         pruneClearedReferences();
  230.     }

  231.     /**
  232.      * Closes this pool, and frees any resources associated with it. Invokes
  233.      * {@link #clear()} to destroy and remove instances in the pool.
  234.      * <p>
  235.      * Calling {@link #addObject} or {@link #borrowObject} after invoking this
  236.      * method on a pool will cause them to throw an
  237.      * {@link IllegalStateException}.
  238.      * </p>
  239.      */
  240.     @Override
  241.     public void close() {
  242.         super.close();
  243.         clear();
  244.     }

  245.     /**
  246.      * Destroys a {@code PooledSoftReference} and removes it from the idle and all
  247.      * references pools.
  248.      *
  249.      * @param toDestroy PooledSoftReference to destroy
  250.      * @throws Exception If an error occurs while trying to destroy the object
  251.      */
  252.     private void destroy(final PooledSoftReference<T> toDestroy) throws Exception {
  253.         toDestroy.invalidate();
  254.         idleReferences.remove(toDestroy);
  255.         allReferences.remove(toDestroy);
  256.         try {
  257.             factory.destroyObject(toDestroy);
  258.         } finally {
  259.             destroyCount++;
  260.             toDestroy.getReference().clear();
  261.         }
  262.     }

  263.     /**
  264.      * Finds the PooledSoftReference in allReferences that points to obj.
  265.      *
  266.      * @param obj returning object
  267.      * @return PooledSoftReference wrapping a soft reference to obj
  268.      */
  269.     private PooledSoftReference<T> findReference(final T obj) {
  270.         final Optional<PooledSoftReference<T>> first = allReferences.stream()
  271.                 .filter(reference -> reference.getObject() != null && reference.getObject().equals(obj)).findFirst();
  272.         return first.orElse(null);
  273.     }

  274.     /**
  275.      * Gets the {@link PooledObjectFactory} used by this pool to create and
  276.      * manage object instances.
  277.      *
  278.      * @return the factory
  279.      */
  280.     public synchronized PooledObjectFactory<T> getFactory() {
  281.         return factory;
  282.     }

  283.     /**
  284.      * Gets the number of instances currently borrowed from this pool.
  285.      *
  286.      * @return the number of instances currently borrowed from this pool
  287.      */
  288.     @Override
  289.     public synchronized int getNumActive() {
  290.         return numActive;
  291.     }

  292.     /**
  293.      * Gets an approximation not less than the of the number of idle
  294.      * instances in the pool.
  295.      *
  296.      * @return estimated number of idle instances in the pool
  297.      */
  298.     @Override
  299.     public synchronized int getNumIdle() {
  300.         pruneClearedReferences();
  301.         return idleReferences.size();
  302.     }

  303.     @Override
  304.     public synchronized void invalidateObject(final T obj) throws Exception {
  305.         final PooledSoftReference<T> ref = findReference(obj);
  306.         if (ref == null) {
  307.             throw new IllegalStateException(
  308.                 "Object to invalidate is not currently part of this pool");
  309.         }
  310.         if (factory != null) {
  311.             destroy(ref);
  312.         }
  313.         numActive--;
  314.         notifyAll(); // numActive has changed
  315.     }

  316.     /**
  317.      * If any idle objects were garbage collected, remove their
  318.      * {@link Reference} wrappers from the idle object pool.
  319.      */
  320.     private void pruneClearedReferences() {
  321.         // Remove wrappers for enqueued references from idle and allReferences lists
  322.         removeClearedReferences(idleReferences.iterator());
  323.         removeClearedReferences(allReferences.iterator());
  324.         while (refQueue.poll() != null) {
  325.             // loop until null
  326.         }
  327.     }

  328.     /**
  329.      * Clears cleared references from iterator's collection
  330.      * @param iterator iterator over idle/allReferences
  331.      */
  332.     private void removeClearedReferences(final Iterator<PooledSoftReference<T>> iterator) {
  333.         PooledSoftReference<T> ref;
  334.         while (iterator.hasNext()) {
  335.             ref = iterator.next();
  336.             if (ref.getReference() == null || ref.getReference().isEnqueued()) {
  337.                 iterator.remove();
  338.             }
  339.         }
  340.     }

  341.     /**
  342.      * Returns an instance to the pool after successful validation and
  343.      * passivation. The returning instance is destroyed if any of the following
  344.      * are true:
  345.      * <ul>
  346.      * <li>the pool is closed</li>
  347.      * <li>{@link PooledObjectFactory#validateObject(
  348.      * org.apache.commons.pool2.PooledObject) validation} fails
  349.      * </li>
  350.      * <li>{@link PooledObjectFactory#passivateObject(
  351.      * org.apache.commons.pool2.PooledObject) passivation}
  352.      * throws an exception</li>
  353.      * </ul>
  354.      * Exceptions passivating or destroying instances are silently swallowed.
  355.      * Exceptions validating instances are propagated to the client.
  356.      *
  357.      * @param obj
  358.      *            instance to return to the pool
  359.      * @throws IllegalArgumentException
  360.      *            if obj is not currently part of this pool
  361.      */
  362.     @Override
  363.     public synchronized void returnObject(final T obj) throws Exception {
  364.         boolean success = !isClosed();
  365.         final PooledSoftReference<T> ref = findReference(obj);
  366.         if (ref == null) {
  367.             throw new IllegalStateException(
  368.                 "Returned object not currently part of this pool");
  369.         }
  370.         if (factory != null) {
  371.             if (!factory.validateObject(ref)) {
  372.                 success = false;
  373.             } else {
  374.                 try {
  375.                     factory.passivateObject(ref);
  376.                 } catch (final Exception e) {
  377.                     success = false;
  378.                 }
  379.             }
  380.         }

  381.         final boolean shouldDestroy = !success;
  382.         numActive--;
  383.         if (success) {

  384.             // Deallocate and add to the idle instance pool
  385.             ref.deallocate();
  386.             idleReferences.add(ref);
  387.         }
  388.         notifyAll(); // numActive has changed

  389.         if (shouldDestroy && factory != null) {
  390.             try {
  391.                 destroy(ref);
  392.             } catch (final Exception ignored) {
  393.                 // ignored
  394.             }
  395.         }
  396.     }

  397.     @Override
  398.     protected void toStringAppendFields(final StringBuilder builder) {
  399.         super.toStringAppendFields(builder);
  400.         builder.append(", factory=");
  401.         builder.append(factory);
  402.         builder.append(", refQueue=");
  403.         builder.append(refQueue);
  404.         builder.append(", numActive=");
  405.         builder.append(numActive);
  406.         builder.append(", destroyCount=");
  407.         builder.append(destroyCount);
  408.         builder.append(", createCount=");
  409.         builder.append(createCount);
  410.         builder.append(", idleReferences=");
  411.         builder.append(idleReferences);
  412.         builder.append(", allReferences=");
  413.         builder.append(allReferences);
  414.     }
  415. }