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

com.palantir.common.pooling.AbstractPoolingContainer Maven / Gradle / Ivy

There is a newer version: 0.1152.0
Show newest version
/*
 * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
 *
 * 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 com.palantir.common.pooling;

import com.google.common.base.Function;
import com.palantir.common.base.FunctionCheckedException;
import com.palantir.common.collect.EmptyQueue;
import com.palantir.logsafe.Preconditions;
import com.palantir.logsafe.SafeArg;
import com.palantir.logsafe.UnsafeArg;
import com.palantir.logsafe.logger.SafeLogger;
import com.palantir.logsafe.logger.SafeLoggerFactory;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nonnull;

/**
 * This class will pool resources up to the passedLimit.  It will never block and instead will always allocate a new
 * resource if there are none in the pool.
 */
public abstract class AbstractPoolingContainer implements PoolingContainer {
    private static final SafeLogger log = SafeLoggerFactory.get(AbstractPoolingContainer.class);

    private volatile Queue pool;
    protected final AtomicLong allocatedResources = new AtomicLong();
    private final int maxPoolSize;

    /**
     * Pool up to itemsToPool resources.  If the internal pool is already full, then additional ones will be
     * thrown away.
     * 

* If items is {@link Integer#MAX_VALUE}, then we will use an unbounded queue with {@link ConcurrentLinkedQueue} * which should have better performance because it is lock-free * @param itemsToPool must be at least 1 */ public AbstractPoolingContainer(int itemsToPool) { if (itemsToPool == Integer.MAX_VALUE) { pool = new ConcurrentLinkedQueue(); } else { pool = new ArrayBlockingQueue(itemsToPool); } this.maxPoolSize = itemsToPool; } public long getAllocatedResources() { return allocatedResources.get(); } public int getMaxPoolSize() { return maxPoolSize; } /** * This should create a new resource. It should be non-null. */ @Nonnull protected abstract T createNewPooledResource(); /** * This will be called after a resource is done being used but the pool is full and it will be discarded *

* This should not throw because it is called in finally blocks. */ protected void cleanupForDiscard(T discardedResource) { // nothing to do here } /** * This will be called after a resource is done being used and will be returned to the pool so it maybe used * again. *

* {@link #cleanupForDiscard(Object)} may be called for this resource after this returns if the * pool was already full or {@link #shutdownPooling()} has been called. *

* This should not throw because it is called in finally blocks. */ protected void cleanupForReturnToPool(T resourceToReturn) { // nothing here } /** * This will run the provided function with a pooled resource. * {@link #createNewPooledResource()} will be called if it cannot get one from the pool. * This function is not allowed to hold onto this resource after it completes. If it does, there will be * undefined behavior. * @return whatever the passed function returns */ @Override public V runWithPooledResource(Function f) { T resource = getResource(); try { return f.apply(resource); } finally { returnResource(resource); } } @Override public V runWithPooledResource(FunctionCheckedException f) throws K { T resource = getResource(); try { return f.apply(resource); } finally { returnResource(resource); } } /** * This should always be followed by a try/finally */ protected final T getResource() { T resource = pool.poll(); if (resource == null) { resource = createNewPooledResource(); Preconditions.checkNotNull(resource, "resource should be non-null"); allocatedResources.incrementAndGet(); } logPoolStats(); return resource; } /** * This method should only be called in the finally block of a try/finally */ protected final void returnResource(T resource) { Preconditions.checkNotNull(resource); try { cleanupForReturnToPool(resource); } catch (RuntimeException e) { log.error("should not throw here", e); } boolean wasReturned = returnToQueue(resource); if (!wasReturned) { log.debug("Pool full, releasing resource: {}", UnsafeArg.of("resource", resource)); allocatedResources.decrementAndGet(); try { cleanupForDiscard(resource); } catch (RuntimeException e) { log.error("should not throw here", e); } } logPoolStats(); } private void logPoolStats() { if (log.isDebugEnabled()) { log.debug( "Allocated {} instances, {} remaining in pool, {} max pool size", SafeArg.of("resourceCount", getAllocatedResources()), SafeArg.of("poolRemaining", pool.size()), SafeArg.of("poolMax", getMaxPoolSize())); } } /** * This method will call {@link #cleanupForDiscard(Object)} on everything that is in the pool and will * make the pool of size zero. Once this method is called using this pool will just result in {@link #createNewPooledResource()} * being called for every use and {@link #cleanupForDiscard(Object)} being called after it is used. */ @Override public void shutdownPooling() { Queue currentPool = pool; pool = EmptyQueue.of(); discardFromPool(currentPool); } /** * This method is used when we still want to continue to pool, but we think that the current pooled resources * are no longer good to use. */ protected void discardCurrentPool() { discardFromPool(pool); } private void discardFromPool(Queue currentPool) { T item = currentPool.poll(); while (item != null) { allocatedResources.decrementAndGet(); try { cleanupForDiscard(item); log.debug("Discarded: {}", UnsafeArg.of("item", item)); } catch (RuntimeException e) { log.error("should not throw here", e); } item = currentPool.poll(); } } private boolean returnToQueue(T resource) { Queue currentPool = pool; boolean ret = currentPool.offer(resource); if (currentPool != pool) { discardFromPool(currentPool); } if (ret) { log.debug("Returned: {}", UnsafeArg.of("resource", resource)); } return ret; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy