All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.integration.util.SimplePool Maven / Gradle / Ivy

There is a newer version: 6.3.3
Show newest version
/*
 * Copyright 2002-2019 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.integration.util;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.util.Assert;

/**
 * Implementation of {@link Pool} supporting dynamic resizing and a variable
 * timeout when attempting to obtain an item from the pool. Pool grows on
 * demand up to the limit.
 *
 * @author Gary Russell
 * @since 2.2
 *
 */
public class SimplePool implements Pool {

	protected final Log logger = LogFactory.getLog(this.getClass());

	private final Semaphore permits = new Semaphore(0);

	private final AtomicInteger poolSize = new AtomicInteger();

	private final AtomicInteger targetPoolSize = new AtomicInteger();

	private long waitTimeout = Long.MAX_VALUE;

	private final BlockingQueue available = new LinkedBlockingQueue();

	private final Set allocated = Collections.synchronizedSet(new HashSet());

	private final Set inUse = Collections.synchronizedSet(new HashSet());

	private final PoolItemCallback callback;

	private volatile boolean closed;

	/**
	 * Creates a SimplePool with a specific limit.
	 * @param poolSize The maximum number of items the pool supports.
	 * @param callback A {@link PoolItemCallback} implementation called during various
	 * pool operations.
	 */
	public SimplePool(int poolSize, PoolItemCallback callback) {
		if (poolSize <= 0) {
			this.poolSize.set(Integer.MAX_VALUE);
			this.targetPoolSize.set(Integer.MAX_VALUE);
			this.permits.release(Integer.MAX_VALUE);
		}
		else {
			this.poolSize.set(poolSize);
			this.targetPoolSize.set(poolSize);
			this.permits.release(poolSize);
		}
		this.callback = callback;
	}

	/**
	 * Adjusts the current pool size. When reducing the pool size, attempts to
	 * remove the delta from the pool. If there are not enough unused items in
	 * the pool, the actual pool size will decrease to the specified size as in-use
	 * items are returned.
	 * @param poolSize The desired target pool size.
	 */
	public synchronized void setPoolSize(int poolSize) {
		int delta = poolSize - this.poolSize.get();
		this.targetPoolSize.addAndGet(delta);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug(String.format("Target pool size changed by %d, now %d", delta, this.targetPoolSize.get()));
		}
		if (delta > 0) {
			this.poolSize.addAndGet(delta);
			this.permits.release(delta);
		}
		else {
			while (delta < 0) {
				if (!this.permits.tryAcquire()) {
					break;
				}
				T item = this.available.poll();
				if (item == null) {
					this.permits.release();
					break;
				}
				doRemoveItem(item);
				this.poolSize.decrementAndGet();
				delta++;
			}
		}
		if (delta < 0 && this.logger.isDebugEnabled()) {
			this.logger.debug(String.format("Pool is overcommitted by %d; items will be removed when returned", -delta));
		}
	}

	/**
	 * Returns the current size of the pool; may be greater than the target pool size
	 * if it was recently reduced and too many items were in use to allow the new size
	 * to be set.
	 */
	@Override
	public synchronized int getPoolSize() {
		return this.poolSize.get();
	}

	@Override
	public int getIdleCount() {
		return this.available.size();
	}

	@Override
	public int getActiveCount() {
		return this.inUse.size();
	}

	@Override
	public int getAllocatedCount() {
		return this.allocated.size();
	}

	/**
	 * Adjusts the wait timeout - the time for which getItem() will wait if no idle
	 * entries are available.
	 * 
* Default: infinity. * @param waitTimeout The wait timeout in milliseconds. */ public void setWaitTimeout(long waitTimeout) { this.waitTimeout = waitTimeout; } /** * Obtains an item from the pool; waits up to waitTime milliseconds (default infinity). * @throws PoolItemNotAvailableException if no items become available in time. */ @Override public T getItem() { Assert.state(!this.closed, "Pool has been closed"); boolean permitted = false; try { try { permitted = this.permits.tryAcquire(this.waitTimeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new PoolItemNotAvailableException("Interrupted awaiting a pooled resource", e); } if (!permitted) { throw new PoolItemNotAvailableException("Timed out while waiting to acquire a pool entry."); } return doGetItem(); } catch (Exception e) { if (permitted) { this.permits.release(); } if (e instanceof PoolItemNotAvailableException) { throw (PoolItemNotAvailableException) e; } throw new PoolItemNotAvailableException("Failed to obtain pooled item", e); } } private T doGetItem() { T item = this.available.poll(); if (item != null && this.logger.isDebugEnabled()) { this.logger.debug("Obtained " + item + " from pool."); } if (item == null) { item = this.callback.createForPool(); if (this.logger.isDebugEnabled()) { this.logger.debug("Obtained new " + item + "."); } this.allocated.add(item); } else if (this.callback.isStale(item)) { if (this.logger.isDebugEnabled()) { this.logger.debug("Received a stale item " + item + ", will attempt to get a new one."); } doRemoveItem(item); item = doGetItem(); } this.inUse.add(item); return item; } /** * Returns an item to the pool. */ @Override public synchronized void releaseItem(T item) { Assert.notNull(item, "Item cannot be null"); Assert.isTrue(this.allocated.contains(item), "You can only release items that were obtained from the pool"); if (this.inUse.contains(item)) { if (this.poolSize.get() > this.targetPoolSize.get() || this.closed) { this.poolSize.decrementAndGet(); if (item != null) { doRemoveItem(item); } } else { if (this.logger.isDebugEnabled()) { this.logger.debug("Releasing " + item + " back to the pool"); } if (item != null) { this.available.add(item); this.inUse.remove(item); } this.permits.release(); } } else { if (this.logger.isDebugEnabled()) { this.logger.debug("Ignoring release of " + item + " back to the pool - not in use"); } } } @Override public synchronized void removeAllIdleItems() { T item; while ((item = this.available.poll()) != null) { doRemoveItem(item); } } private void doRemoveItem(T item) { if (this.logger.isDebugEnabled()) { this.logger.debug("Removing " + item + " from the pool"); } this.allocated.remove(item); this.inUse.remove(item); this.callback.removedFromPool(item); } @Override public synchronized void close() { this.closed = true; removeAllIdleItems(); } /** * User of the pool provide an implementation of this interface; called during * various pool operations. * */ public interface PoolItemCallback { /** * Called by the pool when a new instance is required to populate the pool. Only * called if no idle non-stale instances are available. * @return The item. */ T createForPool(); /** * Called by the pool when an idle item is retrieved from the pool. Indicates * whether that item is usable, or should be discarded. The pool takes no * further action on a stale item, discards it, and attempts to find or create * another item. * @param item The item. * @return true if the item should not be used. */ boolean isStale(T item); /** * Called by the pool when an item is forcibly removed from the pool - for example * when the pool size is reduced. The implementation should perform any cleanup * necessary on the item, such as closing connections etc. * @param item The item. */ void removedFromPool(T item); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy