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 = 0; // @GuardedBy("this")
56  
57      /** Total number of instances that have been destroyed */
58      private long destroyCount = 0; // @GuardedBy("this")
59  
60  
61      /** Total number of instances that have been created */
62      private long createCount = 0; // @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       * Borrows an object from the pool. If there are no idle instances available
83       * in the pool, the configured factory's
84       * {@link PooledObjectFactory#makeObject()} method is invoked to create a
85       * new instance.
86       * <p>
87       * All instances are {@link PooledObjectFactory#activateObject(
88       * org.apache.commons.pool2.PooledObject) activated}
89       * and {@link PooledObjectFactory#validateObject(
90       * org.apache.commons.pool2.PooledObject)
91       * validated} before being returned by this method. If validation fails or
92       * an exception occurs activating or validating an idle instance, the
93       * failing instance is {@link PooledObjectFactory#destroyObject(
94       * org.apache.commons.pool2.PooledObject)
95       * destroyed} and another instance is retrieved from the pool, validated and
96       * activated. This process continues until either the pool is empty or an
97       * instance passes validation. If the pool is empty on activation or it does
98       * not contain any valid instances, the factory's {@code makeObject}
99       * method is used to create a new instance. If the created instance either
100      * raises an exception on activation or fails validation,
101      * {@code NoSuchElementException} is thrown. Exceptions thrown by
102      * {@code MakeObject} are propagated to the caller; but other than
103      * {@code ThreadDeath} or {@code VirtualMachineError}, exceptions
104      * generated by activation, validation or destroy methods are swallowed
105      * silently.
106      *
107      * @throws NoSuchElementException
108      *             if a valid object cannot be provided
109      * @throws IllegalStateException
110      *             if invoked on a {@link #close() closed} pool
111      * @throws Exception
112      *             if an exception occurs creating a new instance
113      * @return a valid, activated object instance
114      */
115     @SuppressWarnings("null") // ref cannot be null
116     @Override
117     public synchronized T borrowObject() throws Exception {
118         assertOpen();
119         T obj = null;
120         boolean newlyCreated = false;
121         PooledSoftReference<T> ref = null;
122         while (null == obj) {
123             if (idleReferences.isEmpty()) {
124                 if (null == factory) {
125                     throw new NoSuchElementException();
126                 }
127                 newlyCreated = true;
128                 obj = factory.makeObject().getObject();
129                 createCount++;
130                 // Do not register with the queue
131                 ref = new PooledSoftReference<>(new SoftReference<>(obj));
132                 allReferences.add(ref);
133             } else {
134                 ref = idleReferences.pollFirst();
135                 obj = ref.getObject();
136                 // Clear the reference so it will not be queued, but replace with a
137                 // a new, non-registered reference so we can still track this object
138                 // in allReferences
139                 ref.getReference().clear();
140                 ref.setReference(new SoftReference<>(obj));
141             }
142             if (null != factory && null != obj) {
143                 try {
144                     factory.activateObject(ref);
145                     if (!factory.validateObject(ref)) {
146                         throw new Exception("ValidateObject failed");
147                     }
148                 } catch (final Throwable t) {
149                     PoolUtils.checkRethrow(t);
150                     try {
151                         destroy(ref);
152                     } catch (final Throwable t2) {
153                         PoolUtils.checkRethrow(t2);
154                         // Swallowed
155                     } finally {
156                         obj = null;
157                     }
158                     if (newlyCreated) {
159                         throw new NoSuchElementException(
160                                 "Could not create a validated object, cause: " +
161                                         t.getMessage());
162                     }
163                 }
164             }
165         }
166         numActive++;
167         ref.allocate();
168         return obj;
169     }
170 
171     /**
172      * Returns an instance to the pool after successful validation and
173      * passivation. The returning instance is destroyed if any of the following
174      * are true:
175      * <ul>
176      * <li>the pool is closed</li>
177      * <li>{@link PooledObjectFactory#validateObject(
178      * org.apache.commons.pool2.PooledObject) validation} fails
179      * </li>
180      * <li>{@link PooledObjectFactory#passivateObject(
181      * org.apache.commons.pool2.PooledObject) passivation}
182      * throws an exception</li>
183      * </ul>
184      * Exceptions passivating or destroying instances are silently swallowed.
185      * Exceptions validating instances are propagated to the client.
186      *
187      * @param obj
188      *            instance to return to the pool
189      * @throws IllegalArgumentException
190      *            if obj is not currently part of this pool
191      */
192     @Override
193     public synchronized void returnObject(final T obj) throws Exception {
194         boolean success = !isClosed();
195         final PooledSoftReference<T> ref = findReference(obj);
196         if (ref == null) {
197             throw new IllegalStateException(
198                 "Returned object not currently part of this pool");
199         }
200         if (factory != null) {
201             if (!factory.validateObject(ref)) {
202                 success = false;
203             } else {
204                 try {
205                     factory.passivateObject(ref);
206                 } catch (final Exception e) {
207                     success = false;
208                 }
209             }
210         }
211 
212         final boolean shouldDestroy = !success;
213         numActive--;
214         if (success) {
215 
216             // Deallocate and add to the idle instance pool
217             ref.deallocate();
218             idleReferences.add(ref);
219         }
220         notifyAll(); // numActive has changed
221 
222         if (shouldDestroy && factory != null) {
223             try {
224                 destroy(ref);
225             } catch (final Exception e) {
226                 // ignored
227             }
228         }
229     }
230 
231     /**
232      * {@inheritDoc}
233      */
234     @Override
235     public synchronized void invalidateObject(final T obj) throws Exception {
236         final PooledSoftReference<T> ref = findReference(obj);
237         if (ref == null) {
238             throw new IllegalStateException(
239                 "Object to invalidate is not currently part of this pool");
240         }
241         if (factory != null) {
242             destroy(ref);
243         }
244         numActive--;
245         notifyAll(); // numActive has changed
246     }
247 
248     /**
249      * Creates an object, and places it into the pool. addObject() is useful for
250      * "pre-loading" a pool with idle objects.
251      * <p>
252      * Before being added to the pool, the newly created instance is
253      * {@link PooledObjectFactory#validateObject(
254      * org.apache.commons.pool2.PooledObject) validated} and
255      * {@link PooledObjectFactory#passivateObject(
256      * org.apache.commons.pool2.PooledObject) passivated}. If
257      * validation fails, the new instance is
258      * {@link PooledObjectFactory#destroyObject(
259      * org.apache.commons.pool2.PooledObject) destroyed}. Exceptions
260      * generated by the factory {@code makeObject} or
261      * {@code passivate} are propagated to the caller. Exceptions
262      * destroying instances are silently swallowed.
263      *
264      * @throws IllegalStateException
265      *             if invoked on a {@link #close() closed} pool
266      * @throws Exception
267      *             when the {@link #getFactory() factory} has a problem creating
268      *             or passivating an object.
269      */
270     @Override
271     public synchronized void addObject() throws Exception {
272         assertOpen();
273         if (factory == null) {
274             throw new IllegalStateException(
275                     "Cannot add objects without a factory.");
276         }
277         final T obj = factory.makeObject().getObject();
278         createCount++;
279         // Create and register with the queue
280         final PooledSoftReference<T> ref = new PooledSoftReference<>(
281                 new SoftReference<>(obj, refQueue));
282         allReferences.add(ref);
283 
284         boolean success = true;
285         if (!factory.validateObject(ref)) {
286             success = false;
287         } else {
288             factory.passivateObject(ref);
289         }
290 
291         final boolean shouldDestroy = !success;
292         if (success) {
293             idleReferences.add(ref);
294             notifyAll(); // numActive has changed
295         }
296 
297         if (shouldDestroy) {
298             try {
299                 destroy(ref);
300             } catch (final Exception e) {
301                 // ignored
302             }
303         }
304     }
305 
306     /**
307      * Returns an approximation not less than the of the number of idle
308      * instances in the pool.
309      *
310      * @return estimated number of idle instances in the pool
311      */
312     @Override
313     public synchronized int getNumIdle() {
314         pruneClearedReferences();
315         return idleReferences.size();
316     }
317 
318     /**
319      * Returns the number of instances currently borrowed from this pool.
320      *
321      * @return the number of instances currently borrowed from this pool
322      */
323     @Override
324     public synchronized int getNumActive() {
325         return numActive;
326     }
327 
328     /**
329      * Clears any objects sitting idle in the pool.
330      */
331     @Override
332     public synchronized void clear() {
333         if (null != factory) {
334             final Iterator<PooledSoftReference<T>> iter = idleReferences.iterator();
335             while (iter.hasNext()) {
336                 try {
337                     final PooledSoftReference<T> ref = iter.next();
338                     if (null != ref.getObject()) {
339                         factory.destroyObject(ref);
340                     }
341                 } catch (final Exception e) {
342                     // ignore error, keep destroying the rest
343                 }
344             }
345         }
346         idleReferences.clear();
347         pruneClearedReferences();
348     }
349 
350     /**
351      * Closes this pool, and frees any resources associated with it. Invokes
352      * {@link #clear()} to destroy and remove instances in the pool.
353      * <p>
354      * Calling {@link #addObject} or {@link #borrowObject} after invoking this
355      * method on a pool will cause them to throw an
356      * {@link IllegalStateException}.
357      */
358     @Override
359     public void close() {
360         super.close();
361         clear();
362     }
363 
364     /**
365      * Returns the {@link PooledObjectFactory} used by this pool to create and
366      * manage object instances.
367      *
368      * @return the factory
369      */
370     public synchronized PooledObjectFactory<T> getFactory() {
371         return factory;
372     }
373 
374     /**
375      * If any idle objects were garbage collected, remove their
376      * {@link Reference} wrappers from the idle object pool.
377      */
378     private void pruneClearedReferences() {
379         // Remove wrappers for enqueued references from idle and allReferences lists
380         removeClearedReferences(idleReferences.iterator());
381         removeClearedReferences(allReferences.iterator());
382         while (refQueue.poll() != null) {
383             // empty
384         }
385     }
386 
387     /**
388      * Finds the PooledSoftReference in allReferences that points to obj.
389      *
390      * @param obj returning object
391      * @return PooledSoftReference wrapping a soft reference to obj
392      */
393     private PooledSoftReference<T> findReference(final T obj) {
394         final Iterator<PooledSoftReference<T>> iterator = allReferences.iterator();
395         while (iterator.hasNext()) {
396             final PooledSoftReference<T> reference = iterator.next();
397             if (reference.getObject() != null && reference.getObject().equals(obj)) {
398                 return reference;
399             }
400         }
401         return null;
402     }
403 
404     /**
405      * Destroys a {@code PooledSoftReference} and removes it from the idle and all
406      * references pools.
407      *
408      * @param toDestroy PooledSoftReference to destroy
409      *
410      * @throws Exception If an error occurs while trying to destroy the object
411      */
412     private void destroy(final PooledSoftReference<T> toDestroy) throws Exception {
413         toDestroy.invalidate();
414         idleReferences.remove(toDestroy);
415         allReferences.remove(toDestroy);
416         try {
417             factory.destroyObject(toDestroy);
418         } finally {
419             destroyCount++;
420             toDestroy.getReference().clear();
421         }
422     }
423 
424     /**
425      * Clears cleared references from iterator's collection
426      * @param iterator iterator over idle/allReferences
427      */
428     private void removeClearedReferences(final Iterator<PooledSoftReference<T>> iterator) {
429         PooledSoftReference<T> ref;
430         while (iterator.hasNext()) {
431             ref = iterator.next();
432             if (ref.getReference() == null || ref.getReference().isEnqueued()) {
433                 iterator.remove();
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 }