View Javadoc
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  
19  import java.lang.ref.Reference;
20  import java.lang.ref.ReferenceQueue;
21  import java.lang.ref.SoftReference;
22  import java.util.ArrayList;
23  import java.util.Iterator;
24  import java.util.NoSuchElementException;
25  import java.util.Optional;
26  
27  import org.apache.commons.pool2.BaseObjectPool;
28  import org.apache.commons.pool2.ObjectPool;
29  import org.apache.commons.pool2.PoolUtils;
30  import org.apache.commons.pool2.PooledObjectFactory;
31  
32  /**
33   * A {@link java.lang.ref.SoftReference SoftReference} based {@link ObjectPool}.
34   * <p>
35   * This class is intended to be thread-safe.
36   * </p>
37   *
38   * @param <T>
39   *            Type of element pooled in this pool.
40   *
41   * @since 2.0
42   */
43  public class SoftReferenceObjectPool<T> extends BaseObjectPool<T> {
44  
45      /** Factory to source pooled objects */
46      private final PooledObjectFactory<T> factory;
47  
48      /**
49       * Queue of broken references that might be able to be removed from
50       * {@code _pool}. This is used to help {@link #getNumIdle()} be more
51       * accurate with minimal performance overhead.
52       */
53      private final ReferenceQueue<T> refQueue = new ReferenceQueue<>();
54  
55      /** Count of instances that have been checkout out to pool clients */
56      private int numActive; // @GuardedBy("this")
57  
58      /** Total number of instances that have been destroyed */
59      private long destroyCount; // @GuardedBy("this")
60  
61  
62      /** Total number of instances that have been created */
63      private long createCount; // @GuardedBy("this")
64  
65      /** Idle references - waiting to be borrowed */
66      private final LinkedBlockingDeque<PooledSoftReference<T>> idleReferences =
67          new LinkedBlockingDeque<>();
68  
69      /** All references - checked out or waiting to be borrowed. */
70      private final ArrayList<PooledSoftReference<T>> allReferences =
71          new ArrayList<>();
72  
73      /**
74       * Constructs a {@code SoftReferenceObjectPool} with the specified factory.
75       *
76       * @param factory object factory to use.
77       */
78      public SoftReferenceObjectPool(final PooledObjectFactory<T> factory) {
79          this.factory = factory;
80      }
81  
82      /**
83       * Creates an object, and places it into the pool. addObject() is useful for
84       * "pre-loading" a pool with idle objects.
85       * <p>
86       * Before being added to the pool, the newly created instance is
87       * {@link PooledObjectFactory#validateObject(
88       * org.apache.commons.pool2.PooledObject) validated} and
89       * {@link PooledObjectFactory#passivateObject(
90       * org.apache.commons.pool2.PooledObject) passivated}. If
91       * validation fails, the new instance is
92       * {@link PooledObjectFactory#destroyObject(
93       * org.apache.commons.pool2.PooledObject) destroyed}. Exceptions
94       * generated by the factory {@code makeObject} or
95       * {@code passivate} are propagated to the caller. Exceptions
96       * destroying instances are silently swallowed.
97       * </p>
98       *
99       * @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 ignored) {
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 ignored) {
242                     // ignored, 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) { // NOPMD
355         }
356     }
357 
358     /**
359      * Clears cleared references from iterator's collection
360      * @param iterator iterator over idle/allReferences
361      */
362     private void removeClearedReferences(final Iterator<PooledSoftReference<T>> iterator) {
363         PooledSoftReference<T> ref;
364         while (iterator.hasNext()) {
365             ref = iterator.next();
366             if (ref.getReference() == null || ref.getReference().isEnqueued()) {
367                 iterator.remove();
368             }
369         }
370     }
371 
372     /**
373      * Returns an instance to the pool after successful validation and
374      * passivation. The returning instance is destroyed if any of the following
375      * are true:
376      * <ul>
377      * <li>the pool is closed</li>
378      * <li>{@link PooledObjectFactory#validateObject(
379      * org.apache.commons.pool2.PooledObject) validation} fails
380      * </li>
381      * <li>{@link PooledObjectFactory#passivateObject(
382      * org.apache.commons.pool2.PooledObject) passivation}
383      * throws an exception</li>
384      * </ul>
385      * Exceptions passivating or destroying instances are silently swallowed.
386      * Exceptions validating instances are propagated to the client.
387      *
388      * @param obj
389      *            instance to return to the pool
390      * @throws IllegalArgumentException
391      *            if obj is not currently part of this pool
392      */
393     @Override
394     public synchronized void returnObject(final T obj) throws Exception {
395         boolean success = !isClosed();
396         final PooledSoftReference<T> ref = findReference(obj);
397         if (ref == null) {
398             throw new IllegalStateException(
399                 "Returned object not currently part of this pool");
400         }
401         if (factory != null) {
402             if (!factory.validateObject(ref)) {
403                 success = false;
404             } else {
405                 try {
406                     factory.passivateObject(ref);
407                 } catch (final Exception e) {
408                     success = false;
409                 }
410             }
411         }
412 
413         final boolean shouldDestroy = !success;
414         numActive--;
415         if (success) {
416 
417             // Deallocate and add to the idle instance pool
418             ref.deallocate();
419             idleReferences.add(ref);
420         }
421         notifyAll(); // numActive has changed
422 
423         if (shouldDestroy && factory != null) {
424             try {
425                 destroy(ref);
426             } catch (final Exception ignored) {
427                 // ignored
428             }
429         }
430     }
431 
432     @Override
433     protected void toStringAppendFields(final StringBuilder builder) {
434         super.toStringAppendFields(builder);
435         builder.append(", factory=");
436         builder.append(factory);
437         builder.append(", refQueue=");
438         builder.append(refQueue);
439         builder.append(", numActive=");
440         builder.append(numActive);
441         builder.append(", destroyCount=");
442         builder.append(destroyCount);
443         builder.append(", createCount=");
444         builder.append(createCount);
445         builder.append(", idleReferences=");
446         builder.append(idleReferences);
447         builder.append(", allReferences=");
448         builder.append(allReferences);
449     }
450 }