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 }