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      /** Total number of instances that have been created */
62      private long createCount; // @GuardedBy("this")
63  
64      /** Idle references - waiting to be borrowed */
65      private final LinkedBlockingDeque<PooledSoftReference<T>> idleReferences =
66          new LinkedBlockingDeque<>();
67  
68      /** All references - checked out or waiting to be borrowed. */
69      private final ArrayList<PooledSoftReference<T>> allReferences =
70          new ArrayList<>();
71  
72      /**
73       * Constructs a {@code SoftReferenceObjectPool} with the specified factory.
74       *
75       * @param factory object factory to use.
76       */
77      public SoftReferenceObjectPool(final PooledObjectFactory<T> factory) {
78          this.factory = factory;
79      }
80  
81      /**
82       * Creates an object, and places it into the pool. addObject() is useful for
83       * "pre-loading" a pool with idle objects.
84       * <p>
85       * Before being added to the pool, the newly created instance is
86       * {@link PooledObjectFactory#validateObject(
87       * org.apache.commons.pool2.PooledObject) validated} and
88       * {@link PooledObjectFactory#passivateObject(
89       * org.apache.commons.pool2.PooledObject) passivated}. If
90       * validation fails, the new instance is
91       * {@link PooledObjectFactory#destroyObject(
92       * org.apache.commons.pool2.PooledObject) destroyed}. Exceptions
93       * generated by the factory {@code makeObject} or
94       * {@code passivate} are propagated to the caller. Exceptions
95       * destroying instances are silently swallowed.
96       * </p>
97       *
98       * @throws IllegalStateException
99       *             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 {@code 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 }