com.metamx.http.client.pool.ResourcePool Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2011 Metamarkets Group Inc.
*
* 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.metamx.http.client.pool;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.metamx.common.logger.Logger;
import java.io.Closeable;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
*/
public class ResourcePool implements Closeable
{
private static final Logger log = new Logger(ResourcePool.class);
private final LoadingCache> pool;
private final AtomicBoolean closed = new AtomicBoolean(false);
public ResourcePool(
final ResourceFactory factory,
final ResourcePoolConfig config
)
{
this.pool = CacheBuilder.newBuilder().build(
new CacheLoader>()
{
@Override
public ImmediateCreationResourceHolder load(K input) throws Exception
{
return new ImmediateCreationResourceHolder(
config.getMaxPerKey(),
config.getUnusedConnectionTimeoutMillis(),
input,
factory
);
}
}
);
}
public ResourceContainer take(final K key)
{
if (closed.get()) {
log.error(String.format("take(%s) called even though I'm closed.", key));
return null;
}
final ImmediateCreationResourceHolder holder;
try {
holder = pool.get(key);
}
catch (ExecutionException e) {
throw Throwables.propagate(e);
}
final V value = holder.get();
return new ResourceContainer()
{
private final AtomicBoolean returned = new AtomicBoolean(false);
@Override
public V get()
{
Preconditions.checkState(!returned.get(), "Resource for key[%s] has been returned, cannot get().", key);
return value;
}
@Override
public void returnResource()
{
if (returned.getAndSet(true)) {
log.warn(String.format("Resource at key[%s] was returned multiple times?", key));
} else {
holder.giveBack(value);
}
}
@Override
protected void finalize() throws Throwable
{
if (!returned.get()) {
log.warn(
String.format(
"Resource[%s] at key[%s] was not returned before Container was finalized, potential resource leak.",
value,
key
)
);
returnResource();
}
super.finalize();
}
};
}
public void close()
{
closed.set(true);
final Map> mapView = pool.asMap();
for (K k : ImmutableSet.copyOf(mapView.keySet())) {
mapView.remove(k).close();
}
}
private static class ImmediateCreationResourceHolder
{
private final int maxSize;
private final K key;
private final ResourceFactory factory;
private final ArrayDeque> resourceHolderList;
private int deficit = 0;
private boolean closed = false;
private final long unusedResourceTimeoutMillis;
private ImmediateCreationResourceHolder(
int maxSize,
long unusedResourceTimeoutMillis,
K key,
ResourceFactory factory
)
{
this.maxSize = maxSize;
this.key = key;
this.factory = factory;
this.unusedResourceTimeoutMillis = unusedResourceTimeoutMillis;
this.resourceHolderList = new ArrayDeque<>();
for (int i = 0; i < maxSize; ++i) {
resourceHolderList.add(new ResourceHolder<>(
System.currentTimeMillis(),
Preconditions.checkNotNull(
factory.generate(key),
"factory.generate(key)"
)
));
}
}
V get()
{
// resourceHolderList can't have nulls, so we'll use a null to signal that we need to create a new resource.
final V poolVal;
synchronized (this) {
while (!closed && resourceHolderList.size() == 0 && deficit == 0) {
try {
this.wait();
}
catch (InterruptedException e) {
Thread.interrupted();
return null;
}
}
if (closed) {
log.info(String.format("get() called even though I'm closed. key[%s]", key));
return null;
} else if (!resourceHolderList.isEmpty()) {
ResourceHolder holder = resourceHolderList.removeFirst();
if (System.currentTimeMillis() - holder.getLastAccessedTime() > unusedResourceTimeoutMillis) {
factory.close(holder.getResource());
poolVal = factory.generate(key);
} else {
poolVal = holder.getResource();
}
} else if (deficit > 0) {
deficit--;
poolVal = null;
} else {
throw new IllegalStateException("WTF?! No objects left, and no object deficit. This is probably a bug.");
}
}
// At this point, we must either return a valid resource or increment "deficit".
final V retVal;
try {
if (poolVal != null && factory.isGood(poolVal)) {
retVal = poolVal;
} else {
if (poolVal != null) {
factory.close(poolVal);
}
retVal = factory.generate(key);
}
}
catch (Throwable e) {
synchronized (this) {
deficit++;
this.notifyAll();
}
throw Throwables.propagate(e);
}
return retVal;
}
void giveBack(V object)
{
Preconditions.checkNotNull(object, "object");
synchronized (this) {
if (closed) {
log.info(String.format("giveBack called after being closed. key[%s]", key));
factory.close(object);
return;
}
if (resourceHolderList.size() >= maxSize) {
if (holderListContains(object)) {
log.warn(
String.format(
"Returning object[%s] at key[%s] that has already been returned!? Skipping",
object,
key
),
new Exception("Exception for stacktrace")
);
} else {
log.warn(
String.format(
"Returning object[%s] at key[%s] even though we already have all that we can hold[%s]!? Skipping",
object,
key,
resourceHolderList
),
new Exception("Exception for stacktrace")
);
}
return;
}
resourceHolderList.addLast(new ResourceHolder<>(System.currentTimeMillis(), object));
this.notifyAll();
}
}
private boolean holderListContains(V object)
{
return resourceHolderList.stream().anyMatch(a -> a.getResource().equals(object));
}
void close()
{
synchronized (this) {
closed = true;
resourceHolderList.forEach(v -> factory.close(v.getResource()));
resourceHolderList.clear();
this.notifyAll();
}
}
}
private static class ResourceHolder
{
private long lastAccessedTime;
private V resource;
public ResourceHolder(long lastAccessedTime, V resource)
{
this.resource = resource;
this.lastAccessedTime = lastAccessedTime;
}
public long getLastAccessedTime()
{
return lastAccessedTime;
}
public V getResource()
{
return resource;
}
}
}