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  
26  import org.apache.commons.pool2.BaseObjectPool;
27  import org.apache.commons.pool2.ObjectPool;
28  import org.apache.commons.pool2.PoolUtils;
29  import org.apache.commons.pool2.PooledObjectFactory;
30  
31  /**
32   * A {@link java.lang.ref.SoftReference SoftReference} based {@link ObjectPool}.
33   * <p>
34   * This class is intended to be thread-safe.
35   * </p>
36   *
37   * @param <T>
38   *            Type of element pooled in this pool.
39   *
40   * @since 2.0
41   */
42  public class SoftReferenceObjectPool<T> extends BaseObjectPool<T> {
43  
44      /** Factory to source pooled objects */
45      private final PooledObjectFactory<T> factory;
46  
47      /**
48       * Queue of broken references that might be able to be removed from
49       * {@code _pool}. This is used to help {@link #getNumIdle()} be more
50       * accurate with minimal performance overhead.
51       */
52      private final ReferenceQueue<T> refQueue = new ReferenceQueue<>();
53  
54      /** Count of instances that have been checkout out to pool clients */
55      private int numActive; // @GuardedBy("this")
56  
57      /** Total number of instances that have been destroyed */
58      private long destroyCount; // @GuardedBy("this")
59  
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       * Create 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       *
97       * @throws IllegalStateException
98       *             if invoked on a {@link #close() closed} pool
99       * @throws Exception
100      *             when the {@link #getFactory() factory} has a problem creating
101      *             or passivating an object.
102      */
103     @Override
104     public synchronized void addObject() throws Exception {
105         assertOpen();
106         if (factory == null) {
107             throw new IllegalStateException(
108                     "Cannot add objects without a factory.");
109         }
110         final T obj = factory.makeObject().getObject();
111         createCount++;
112         // Create and register with the queue
113         final PooledSoftReference<T> ref = new PooledSoftReference<>(
114                 new SoftReference<>(obj, refQueue));
115         allReferences.add(ref);
116 
117         boolean success = true;
118         if (!factory.validateObject(ref)) {
119             success = false;
120         } else {
121             factory.passivateObject(ref);
122         }
123 
124         final boolean shouldDestroy = !success;
125         if (success) {
126             idleReferences.add(ref);
127             notifyAll(); // numActive has changed
128         }
129 
130         if (shouldDestroy) {
131             try {
132                 destroy(ref);
133             } catch (final Exception e) {
134                 // ignored
135             }
136         }
137     }
138 
139     /**
140      * Borrows an object from the pool. If there are no idle instances available
141      * in the pool, the configured factory's
142      * {@link PooledObjectFactory#makeObject()} method is invoked to create a
143      * new instance.
144      * <p>
145      * All instances are {@link PooledObjectFactory#activateObject(
146      * org.apache.commons.pool2.PooledObject) activated}
147      * and {@link PooledObjectFactory#validateObject(
148      * org.apache.commons.pool2.PooledObject)
149      * validated} before being returned by this method. If validation fails or
150      * an exception occurs activating or validating an idle instance, the
151      * failing instance is {@link PooledObjectFactory#destroyObject(
152      * org.apache.commons.pool2.PooledObject)
153      * destroyed} and another instance is retrieved from the pool, validated and
154      * activated. This process continues until either the pool is empty or an
155      * instance passes validation. If the pool is empty on activation or it does
156      * not contain any valid instances, the factory's {@code makeObject}
157      * method is used to create a new instance. If the created instance either
158      * raises an exception on activation or fails validation,
159      * {@code NoSuchElementException} is thrown. Exceptions thrown by
160      * {@code MakeObject} are propagated to the caller; but other than
161      * {@code ThreadDeath} or {@code VirtualMachineError}, exceptions
162      * generated by activation, validation or destroy methods are swallowed
163      * silently.
164      *
165      * @throws NoSuchElementException
166      *             if a valid object cannot be provided
167      * @throws IllegalStateException
168      *             if invoked on a {@link #close() closed} pool
169      * @throws Exception
170      *             if an exception occurs creating a new instance
171      * @return a valid, activated object instance
172      */
173     @SuppressWarnings("null") // ref cannot be null
174     @Override
175     public synchronized T borrowObject() throws Exception {
176         assertOpen();
177         T obj = null;
178         boolean newlyCreated = false;
179         PooledSoftReference<T> ref = null;
180         while (null == obj) {
181             if (idleReferences.isEmpty()) {
182                 if (null == factory) {
183                     throw new NoSuchElementException();
184                 }
185                 newlyCreated = true;
186                 obj = factory.makeObject().getObject();
187                 createCount++;
188                 // Do not register with the queue
189                 ref = new PooledSoftReference<>(new SoftReference<>(obj));
190                 allReferences.add(ref);
191             } else {
192                 ref = idleReferences.pollFirst();
193                 obj = ref.getObject();
194                 // Clear the reference so it will not be queued, but replace with a
195                 // a new, non-registered reference so we can still track this object
196                 // in allReferences
197                 ref.getReference().clear();
198                 ref.setReference(new SoftReference<>(obj));
199             }
200             if (null != factory && null != obj) {
201                 try {
202                     factory.activateObject(ref);
203                     if (!factory.validateObject(ref)) {
204                         throw new Exception("ValidateObject failed");
205                     }
206                 } catch (final Throwable t) {
207                     PoolUtils.checkRethrow(t);
208                     try {
209                         destroy(ref);
210                     } catch (final Throwable t2) {
211                         PoolUtils.checkRethrow(t2);
212                         // Swallowed
213                     } finally {
214                         obj = null;
215                     }
216                     if (newlyCreated) {
217                         throw new NoSuchElementException(
218                                 "Could not create a validated object, cause: " +
219                                         t.getMessage());
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             final Iterator<PooledSoftReference<T>> iter = idleReferences.iterator();
236             while (iter.hasNext()) {
237                 try {
238                     final PooledSoftReference<T> ref = iter.next();
239                     if (null != ref.getObject()) {
240                         factory.destroyObject(ref);
241                     }
242                 } catch (final Exception e) {
243                     // ignore error, keep destroying the rest
244                 }
245             }
246         }
247         idleReferences.clear();
248         pruneClearedReferences();
249     }
250 
251     /**
252      * Closes this pool, and frees any resources associated with it. Invokes
253      * {@link #clear()} to destroy and remove instances in the pool.
254      * <p>
255      * Calling {@link #addObject} or {@link #borrowObject} after invoking this
256      * method on a pool will cause them to throw an
257      * {@link IllegalStateException}.
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 Iterator<PooledSoftReference<T>> iterator = allReferences.iterator();
293         while (iterator.hasNext()) {
294             final PooledSoftReference<T> reference = iterator.next();
295             if (reference.getObject() != null && reference.getObject().equals(obj)) {
296                 return reference;
297             }
298         }
299         return null;
300     }
301 
302     /**
303      * Gets the {@link PooledObjectFactory} used by this pool to create and
304      * manage object instances.
305      *
306      * @return the factory
307      */
308     public synchronized PooledObjectFactory<T> getFactory() {
309         return factory;
310     }
311 
312     /**
313      * Gets the number of instances currently borrowed from this pool.
314      *
315      * @return the number of instances currently borrowed from this pool
316      */
317     @Override
318     public synchronized int getNumActive() {
319         return numActive;
320     }
321 
322     /**
323      * Gets an approximation not less than the of the number of idle
324      * instances in the pool.
325      *
326      * @return estimated number of idle instances in the pool
327      */
328     @Override
329     public synchronized int getNumIdle() {
330         pruneClearedReferences();
331         return idleReferences.size();
332     }
333 
334     /**
335      * {@inheritDoc}
336      */
337     @Override
338     public synchronized void invalidateObject(final T obj) throws Exception {
339         final PooledSoftReference<T> ref = findReference(obj);
340         if (ref == null) {
341             throw new IllegalStateException(
342                 "Object to invalidate is not currently part of this pool");
343         }
344         if (factory != null) {
345             destroy(ref);
346         }
347         numActive--;
348         notifyAll(); // numActive has changed
349     }
350 
351     /**
352      * If any idle objects were garbage collected, remove their
353      * {@link Reference} wrappers from the idle object pool.
354      */
355     private void pruneClearedReferences() {
356         // Remove wrappers for enqueued references from idle and allReferences lists
357         removeClearedReferences(idleReferences.iterator());
358         removeClearedReferences(allReferences.iterator());
359         while (refQueue.poll() != null) {
360             // empty
361         }
362     }
363 
364     /**
365      * Clears cleared references from iterator's collection
366      * @param iterator iterator over idle/allReferences
367      */
368     private void removeClearedReferences(final Iterator<PooledSoftReference<T>> iterator) {
369         PooledSoftReference<T> ref;
370         while (iterator.hasNext()) {
371             ref = iterator.next();
372             if (ref.getReference() == null || ref.getReference().isEnqueued()) {
373                 iterator.remove();
374             }
375         }
376     }
377 
378     /**
379      * Returns an instance to the pool after successful validation and
380      * passivation. The returning instance is destroyed if any of the following
381      * are true:
382      * <ul>
383      * <li>the pool is closed</li>
384      * <li>{@link PooledObjectFactory#validateObject(
385      * org.apache.commons.pool2.PooledObject) validation} fails
386      * </li>
387      * <li>{@link PooledObjectFactory#passivateObject(
388      * org.apache.commons.pool2.PooledObject) passivation}
389      * throws an exception</li>
390      * </ul>
391      * Exceptions passivating or destroying instances are silently swallowed.
392      * Exceptions validating instances are propagated to the client.
393      *
394      * @param obj
395      *            instance to return to the pool
396      * @throws IllegalArgumentException
397      *            if obj is not currently part of this pool
398      */
399     @Override
400     public synchronized void returnObject(final T obj) throws Exception {
401         boolean success = !isClosed();
402         final PooledSoftReference<T> ref = findReference(obj);
403         if (ref == null) {
404             throw new IllegalStateException(
405                 "Returned object not currently part of this pool");
406         }
407         if (factory != null) {
408             if (!factory.validateObject(ref)) {
409                 success = false;
410             } else {
411                 try {
412                     factory.passivateObject(ref);
413                 } catch (final Exception e) {
414                     success = false;
415                 }
416             }
417         }
418 
419         final boolean shouldDestroy = !success;
420         numActive--;
421         if (success) {
422 
423             // Deallocate and add to the idle instance pool
424             ref.deallocate();
425             idleReferences.add(ref);
426         }
427         notifyAll(); // numActive has changed
428 
429         if (shouldDestroy && factory != null) {
430             try {
431                 destroy(ref);
432             } catch (final Exception e) {
433                 // ignored
434             }
435         }
436     }
437 
438     @Override
439     protected void toStringAppendFields(final StringBuilder builder) {
440         super.toStringAppendFields(builder);
441         builder.append(", factory=");
442         builder.append(factory);
443         builder.append(", refQueue=");
444         builder.append(refQueue);
445         builder.append(", numActive=");
446         builder.append(numActive);
447         builder.append(", destroyCount=");
448         builder.append(destroyCount);
449         builder.append(", createCount=");
450         builder.append(createCount);
451         builder.append(", idleReferences=");
452         builder.append(idleReferences);
453         builder.append(", allReferences=");
454         builder.append(allReferences);
455     }
456 }