/* * Copyright (C) 2011-2014 Chris Vest (mr.chrisvest@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package stormpot; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** * = The basic configuration "bean" class. * * Instances of this class is passed to the constructors of {@link Pool pools} * so that they know how big they should be, how to allocate objects and when * to deallocate objects. * * This class is made thread-safe by having the fields be protected by the * intrinsic object lock on the Config object itself. This way, pools can * `synchronize` on the config object to read the values out * atomically. * * The various set* methods are made to return the Config instance itself, so * that the method calls may be chained if so desired. * * = Standardised configuration * * The contract of the Config class, and how Pools will interpret it, is within * the context of a so-called standardised configuration. All pool and Config * implementations must behave similarly in a standardised configuration. * * It is conceivable that some pool implementations will come with their own * sub-classes of Config, that allow greater control over the pools behaviour. * It is even permissible that these pool implementations may * deviate from the contract of the Pool interface. However, they are only * allowed to do so in a non-standard configuration. That is, any deviation * from the specified contracts must be explicitly enabled. * * @author Chris Vest <mr.chrisvest@gmail.com> * @param <T> The type of {@link Poolable} objects that a {@link Pool} based * on this Config will produce. */ @SuppressWarnings("unchecked") public class Config<T extends Poolable> implements Cloneable { private int size = 10; private Expiration<? super T> expiration = new TimeSpreadExpiration<T>(480000, 600000, TimeUnit.MILLISECONDS); // 8 to 10 minutes private Allocator<?> allocator; private MetricsRecorder metricsRecorder; private ThreadFactory threadFactory = StormpotThreadFactory.INSTANCE; private boolean preciseLeakDetectionEnabled = true; private boolean backgroundExpirationEnabled = false; /** * Build a new empty Config object. Most settings have reasonable default * values. However, no {@link Allocator} is configured by default, and one * must make sure to set one. */ public Config() { } /** * Set the size of the pools we want to configure them with. Pools are * required to control the allocations and deallocations, such that no more * than this number of objects are allocated at any time. * * This means that a pool of size 1, whose single object have expired, will * deallocate that one object before allocating a replacement. * * The size must be at least one for standard pool configurations. A Pool * will throw an {@link IllegalArgumentException} from their constructor if * this is not the case. * @param size The target pool size. Must be at least 1. * @return This Config instance. */ public synchronized Config<T> setSize(int size) { this.size = size; return this; } /** * Get the currently configured size. Default is 10. * @return The configured pool size. */ public synchronized int getSize() { return size; } /** * Set the {@link Allocator} or {@link Reallocator} to use for the pools we * want to configure. This will change the type-parameter of the Config * object to match that of the new Allocator. * * The allocator is initially `null` in a new Config object, and can be set * to `null` any time. However, in a standard configuration, it must be * non-null when the Config is passed to a Pool constructor. Otherwise the * constructor will throw an {@link IllegalArgumentException}. * @param allocator The allocator we want our pools to use. * @param <X> The type of {@link Poolable} that is created by the allocator, * and the type of objects that the configured pools will contain. * @return This Config instance, but with a generic type parameter that * matches that of the allocator. */ public synchronized <X extends Poolable> Config<X> setAllocator( Allocator<X> allocator) { this.allocator = allocator; return (Config<X>) this; } /** * Get the configured {@link Allocator} instance. There is no configured * allocator by default, so this must be {@link #setAllocator(Allocator) set} * before instantiating any Pool implementations from this Config. * @return The configured Allocator instance, if any. */ public synchronized Allocator<T> getAllocator() { return (Allocator<T>) allocator; } /** * Get the configured {@link stormpot.Allocator} instance as a * {@link stormpot.Reallocator}. If the configured allocator implements the * Reallocator interface, then it is returned directly. Otherwise, the * allocator is wrapped in an adaptor. * @return A configured or adapted Reallocator, if any. */ public synchronized Reallocator<T> getReallocator() { if (allocator == null) { return null; } if (allocator instanceof Reallocator) { return (Reallocator<T>) allocator; } return new ReallocatingAdaptor<T>((Allocator<T>) allocator); } /** * Set the {@link Expiration} to use for the pools we want to * configure. The Expiration determines when a pooled object is valid * for claiming, or when the objects are invalid and should be deallocated. * * The default Expiration is a {@link TimeSpreadExpiration} that * invalidates the objects after they have been active for somewhere between * 8 to 10 minutes. * @param expiration The expiration we want our pools to use. Not null. * @return This Config instance. */ public synchronized Config<T> setExpiration(Expiration<? super T> expiration) { this.expiration = expiration; return this; } /** * Get the configured {@link Expiration} instance. The default is a * {@link TimeSpreadExpiration} that expires objects after somewhere between * 8 to 10 minutes. * @return The configured Expiration. */ public synchronized Expiration<? super T> getExpiration() { return expiration; } /** * Set the {@link MetricsRecorder} to use for the pools we want to configure. * @param metricsRecorder The MetricsRecorder to use, or null if we don't * want to use any. * @return This Config instance. */ public synchronized Config<T> setMetricsRecorder(MetricsRecorder metricsRecorder) { this.metricsRecorder = metricsRecorder; return this; } /** * Get the configured {@link MetricsRecorder} instance, or null if none has * been configured. * @return The configured MetricsRecorder. */ public synchronized MetricsRecorder getMetricsRecorder() { return metricsRecorder; } /** * Get the ThreadFactory that has been configured, and will be used to create * the background allocation threads for the pools. The default is similar to * the {@link java.util.concurrent.Executors#defaultThreadFactory()}, except * the string "Stormpot-" is prepended to the thread name. * @return The configured thread factory. */ public synchronized ThreadFactory getThreadFactory() { return threadFactory; } /** * Set the ThreadFactory that the pools will use to create its background * threads with. The ThreadFactory is not allowed to be null, and creating * a pool with a null ThreadFactory will throw an IllegalArgumentException. * @param factory The ThreadFactory the pool should use to create their * background threads. * @return This Config instance. */ public synchronized Config<T> setThreadFactory(ThreadFactory factory) { threadFactory = factory; return this; } /** * Return whether or not precise object leak detection is enabled, which is * the case by default. * @return `true` if precise object leak detection is enabled. * @see #setPreciseLeakDetectionEnabled(boolean) */ public synchronized boolean isPreciseLeakDetectionEnabled() { return preciseLeakDetectionEnabled; } /** * Enable or disable precise object leak detection. It is enabled by default. * Precise object leak detection makes the pool keep an eye on the allocated * Poolables, such that it notices if they get garbage collected without * first being deallocated. Using the garbage collector for this purpose, * means that no false positives (counting objects as leaked, even though * they are not) are ever reported. * * NOTE: While the pool is able to detect object leaks, it cannot prevent * them. All leaks are a sign that there is a bug in the system; most likely * a bug in your code, or in the way the pool is used. * * Precise object leak detection incurs virtually no overhead, and is safe to * leave enabled at all times – even in the most demanding production * environments. * * @param enabled `true` to turn on precise object leak detection (the * default) `false` to turn it off. * @return This Config instance. */ public synchronized Config<T> setPreciseLeakDetectionEnabled(boolean enabled) { this.preciseLeakDetectionEnabled = enabled; return this; } /** * Return whether or not background expiration is enabled, which it is not by * default. * @return `true` if background expiration has been enabled. * @see #setBackgroundExpirationEnabled(boolean) */ public synchronized boolean isBackgroundExpirationEnabled() { return backgroundExpirationEnabled; } /** * Enable or disable background object expiration checking. This is disabled * by default, because the pool does not know how expensive this check is. * The cost of the check matters because it might end up taking resources * away from the background thread and hinder its ability to keep up with the * demand for allocations and deallocations, even though these tasks always * take priority over any expiration checking. * * @param enabled `true` to turn background expiration checking on, `false` * (the default) to turn it off. * @return This Config instance. */ public synchronized Config<T> setBackgroundExpirationEnabled(boolean enabled) { backgroundExpirationEnabled = enabled; return this; } /** * Returns a shallow copy of this Config object. * @return A new Config object of the exact same type as this one, with * identical values in all its fields. */ @Override public final synchronized Config<T> clone() { try { return (Config<T>) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(e); } } /** * Check that the configuration is valid in terms of the *standard * configuration*. This method is useful in the * Pool implementation constructors. * @throws IllegalArgumentException If the size is less than one, if the * {@link Expiration} is `null`, if the {@link Allocator} is `null`, or if * the ThreadFactory is `null`. */ synchronized void validate() throws IllegalArgumentException { if (size < 1) { throw new IllegalArgumentException( "Size must be at least 1, but was " + size); } if (allocator == null) { throw new IllegalArgumentException("Allocator cannot be null"); } if (expiration == null) { throw new IllegalArgumentException("Expiration cannot be null"); } if (threadFactory == null) { throw new IllegalArgumentException("ThreadFactory cannot be null"); } } /** * Returns `null` if no allocator has been configured. * Otherwise returns a `Reallocator`, possibly by adapting the configured * `Allocator` if need be. * If a `MetricsRecorder` has been configured, the return `Reallocator` will * automatically record allocation, reallocation and deallocation latencies. */ Reallocator<T> getAdaptedReallocator() { if (allocator == null) { return null; } if (metricsRecorder == null) { if (allocator instanceof Reallocator) { return (Reallocator<T>) allocator; } return new ReallocatingAdaptor<T>((Allocator<T>) allocator); } else { if (allocator instanceof Reallocator) { return new TimingReallocatorAdaptor<T>( (Reallocator<T>) allocator, metricsRecorder); } return new TimingReallocatingAdaptor<T>( (Allocator<T>) allocator, metricsRecorder); } } }