/*
* 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.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* A Pool is a self-renewable set of objects from which one can claim exclusive
* access to elements, until they are released back into the pool.
*
* Pools contain {@link Poolable} objects. When you claim an object in a pool,
* you also take upon yourself the responsibility of eventually
* {@link Poolable#release() releasing} that object again. By far the most
* common idiom to achieve this is with a `try-finally` clause:
*
* [source,java]
* --
* Timeout timeout = new Timeout(1, TimeUnit.SECONDS);
* SomePoolable obj = pool.claim(timeout);
* try {
* // Do useful things with 'obj'.
* // Note that 'obj' will be 'null' if 'claim' timed out.
* } finally {
* if (obj != null) {
* obj.release();
* }
* }
* --
*
* The pools are resizable, and can have their capacity changed at any time
* after they have been created.
*
* All pools are configured with a certain size, the number of objects that the
* pool will have allocated at any one time, and they can change this number on
* the fly using the {@link #setTargetSize(int)} method. The change does not
* take effect immediately, but instead moves the "goal post" and leaves the
* pool to move towards it at its own pace.
*
* No guarantees can be made about when the pool will actually reach the target
* size, because it might depend on how long it takes for a certain number of
* objects to be released back into the pool.
*
* Pools are themselves lifecycled, and should be {@link #shutdown() shut down}
* when they are no longer needed.
*
* Note that Pools are not guaranteed to have overwritten the
* {@link Object#finalize()} method. Pools are expected to rely on explicit
* clean-up for releasing their resources.
*
* @author Chris Vest <mr.chrisvest@gmail.com>
* @param <T> the type of {@link Poolable} contained in the pool, as determined
* by the {@link Config#setAllocator(Allocator) configured allocator}.
*/
public abstract class Pool<T extends Poolable> {
/**
* Claim the exclusive rights until released, to an object in the pool.
* Possibly waiting up to the specified amount of time, as given by the
* provided {@link Timeout} instance, for one to become available if the
* pool has been depleted. If the timeout elapses before an object can be
* claimed, then `null` is returned instead. The timeout will be
* honoured even if the Allocators {@link Allocator#allocate(Slot) allocate}
* methods blocks forever. If the given timeout has a zero or negative value,
* then the method will not wait.
*
* If the current thread has already one or more objects currently claimed,
* then a distinct object will be returned, if one is or becomes available.
* This means that it is possible for a single thread to deplete the pool, if
* it so desires. However, doing so is inherently deadlock prone, so avoid
* claiming more than one object at a time per thread, if at all possible.
*
* This method may throw a PoolException if the pool have trouble allocating
* objects. That is, if its assigned Allocator throws exceptions from its
* allocate method, or returns `null`.
*
* An {@link InterruptedException} will be thrown if the thread has its
* interrupted flag set upon entry to this method, or is interrupted while
* waiting. The interrupted flag on the thread will be cleared after
* this, as per the general contract of interruptible methods.
*
* If the pool has been shut down, then an {@link IllegalStateException} will
* be thrown when this method is called. Likewise if we are waiting for an
* object to become available, and someone shuts the pool down.
*
* Here's an example code snippet, where an object is claimed, printed to
* `System.out`, and then released back to the pool:
*
* [source,java]
* --
* Poolable obj = pool.claim(TIMEOUT);
* if (obj != null) {
* try {
* System.out.println(obj);
* } finally {
* obj.release();
* }
* }
* --
*
* Memory effects:
*
* * The {@link Poolable#release() release} of an object happens-before
* any subsequent claim or {@link Allocator#deallocate(Poolable)
* deallocation} of that object, and,
* * The {@link Allocator#allocate(Slot) allocation} of an object
* happens-before any claim of that object.
*
* @param timeout The timeout of the maximum permitted time-slice to wait for
* an object to become available. A timeout with a value of zero or less
* means that the call will do no waiting, preferring instead to return early
* if no objects are available.
* @return An object of the Poolable subtype T to which the exclusive rights
* have been claimed, or `null` if the timeout period elapsed
* before an object became available.
* @throws PoolException If an object allocation failed because the Allocator
* threw an exception from its allocate method, or returned
* `null`, or the
* {@link Expiration#hasExpired(SlotInfo) expiration check} threw an
* exception.
* @throws InterruptedException if the current thread is
* {@link Thread#interrupt() interrupted} upon entry, or becomes interrupted
* while waiting.
* @throws IllegalArgumentException if the `timeout` argument is `null`.
*/
public abstract T claim(Timeout timeout)
throws PoolException, InterruptedException;
/**
* Initiate the shut down process on this pool, and return a
* {@link Completion} instance representing the shut down procedure.
*
* The shut down process is asynchronous, and the shutdown method is
* guaranteed to not wait for any claimed {@link Poolable Poolables} to
* be released.
*
* The shut down process cannot complete before all Poolables are released
* back into the pool and {@link Allocator#deallocate(Poolable) deallocated},
* and all internal resources (such as threads, for instance) have been
* released as well. Only when all of these things have been taken care of,
* does the await methods of the Completion return.
*
* Once the shut down process has been initiated, that is, as soon as this
* method is called, the pool can no longer be used and all calls to
* {@link #claim(Timeout)} will throw an {@link IllegalStateException}.
* Threads that are already waiting for objects in the claim method, will
* also wake up and receive an {@link IllegalStateException}.
*
* All objects that are already claimed when this method is called,
* will continue to function until they are
* {@link Poolable#release() released}.
*
* The shut down process is guaranteed to never deallocate objects that are
* currently claimed. Their deallocation will wait until they are released.
* @return A {@link Completion} instance that represents the shut down
* process.
*/
public abstract Completion shutdown();
/**
* Set the target size for this pool. The pool will strive to keep this many
* objects allocated at any one time.
*
* If the new target size is greater than the old one, the pool will allocate
* more objects until it reaches the target size. If, on the other hand, the
* new target size is less than the old one, the pool will deallocate more
* and allocate less, until the new target size is reached.
*
* No guarantees are made about when the pool actually reaches the target
* size. In fact, it may never happen as the target size can be changed as
* often as one sees fit.
*
* Pools that do not support a size less than 1 (which would deviate from the
* standard configuration space) will throw an
* {@link IllegalArgumentException} if passed 0 or less.
* @param size The new target size of the pool
*/
public abstract void setTargetSize(int size);
/**
* Get the currently configured target size of the pool. Note that this is
* _not_ the number of objects currently allocated by the pool - only
* the number of allocations the pool strives to keep alive.
* @return The current target size of this pool.
*/
public abstract int getTargetSize();
/**
* Claim an object from the pool and apply the given function to it, returning
* the result and releasing the object back to the pool.
*
* If an object cannot be claimed within the given timeout, then
* {@link Optional#empty()} is returned instead. The `empty()` value is also
* returned if the function returns `null`.
*
* @param timeout The timeout of the maximum permitted amount of time to wait
* for an object to become available. A timeout with a value of zero or less
* means that the call will do no waiting, preferring instead to return early
* if no objects are available.
* @param function The function to apply to the claimed object, if any. The
* function should avoid further claims, since having more than one object
* claimed at a time per thread is inherently deadlock prone.
* @param <R> The return type of the given function.
* @return an {@link Optional} of either the return value of applying the
* given function to a claimed object, or empty if the timeout elapsed or
* the function returned `null`.
* @throws InterruptedException if the thread was interrupted.
* @see #claim(Timeout) for more details on failure modes and memory effects.
*/
public final <R> Optional<R> apply(Timeout timeout, Function<T, R> function)
throws InterruptedException {
T obj = claim(timeout);
if (obj == null) {
return Optional.empty();
}
try {
return Optional.ofNullable(function.apply(obj));
} finally {
obj.release();
}
}
/**
* Claim an object from the pool and supply it to the given consumer, and then
* release it back to the pool.
*
* If an object cannot be claimed within the given timeout, then this method
* returns `false`. Otherwise, if an object was claimed and supplied to the
* consumer, the method returns `true`.
* @param timeout The timeout of the maximum permitted amount of time to wait
* for an object to become available. A timeout with a value of zero or less
* means that the call will do no waiting, preferring instead to return early
* if no objects are available.
* @param consumer The consumer to pass the claimed object to, if any. The
* consumer should avoid further claims, since having more than one object
* claimed at a time per thread is inherently deadlock prone.
* @return `true` if an object could be claimed within the given timeout and
* passed to the given consumer, or `false` otherwise.
* @throws InterruptedException if the thread was interrupted.
* @see #claim(Timeout) for more details on failure modes and memory effects.
*/
public final boolean supply(Timeout timeout, Consumer<T> consumer)
throws InterruptedException {
T obj = claim(timeout);
if (obj == null) {
return false;
}
try {
consumer.accept(obj);
return true;
} finally {
obj.release();
}
}
}