/*
Copyright (c) 2012 LinkedIn Corp.
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.
*/
/**
* $Id: $
*/
package com.linkedin.r2.transport.http.client;
import com.linkedin.common.callback.Callback;
import com.linkedin.common.callback.Callbacks;
import com.linkedin.common.util.None;
import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author Steven Ihde
* @version $Revision: $
*/
class ChannelPoolManager implements PoolStatsProvider
{
private static final Logger LOG = LoggerFactory.getLogger(ChannelPoolManager.class);
public static final String BASE_NAME = "ChannelPools";
// All modifications of _pool and all access to _state must be locked on _mutex.
// READS of _pool are allowed without synchronization
private final Object _mutex = new Object();
// We set update concurrency to 1 because all updates occur in a synchronized block
private final ConcurrentMap<SocketAddress,AsyncPool<Channel>> _pool =
new ConcurrentHashMap<SocketAddress,AsyncPool<Channel>>(256, 0.75f, 1);
private enum State { RUNNING, SHUTTING_DOWN, SHUTDOWN }
private State _state = State.RUNNING;
private final ChannelPoolFactory _channelPoolFactory;
private final String _name;
public ChannelPoolManager(ChannelPoolFactory channelPoolFactory)
{
this(channelPoolFactory,
HttpClientFactory.DEFAULT_CLIENT_NAME + BASE_NAME);
}
public ChannelPoolManager(ChannelPoolFactory channelPoolFactory,
String name)
{
_channelPoolFactory = channelPoolFactory;
_name = name;
}
public void shutdown(final Callback<None> callback)
{
final Collection<AsyncPool<Channel>> pools;
final State state;
synchronized (_mutex)
{
state = _state;
pools = _pool.values();
if (state == State.RUNNING)
{
_state = State.SHUTTING_DOWN;
}
}
if (state != State.RUNNING)
{
callback.onError(new IllegalStateException("ChannelPoolManager is " + state));
return;
}
LOG.info("Shutting down {} connection pools", pools.size());
Callback<None> poolCallback = Callbacks.countDown(new Callback<None>()
{
@Override
public void onSuccess(None none)
{
synchronized (_mutex)
{
_state = State.SHUTDOWN;
}
LOG.info("All connection pools shutdown");
callback.onSuccess(None.none());
}
@Override
public void onError(Throwable e)
{
synchronized (_mutex)
{
_state = State.SHUTDOWN;
}
LOG.error("Error shutting down connection pools", e);
callback.onError(e);
}
}, pools.size());
for (AsyncPool<Channel> pool : pools)
{
pool.shutdown(poolCallback);
}
}
public Collection<Callback<Channel>> cancelWaiters()
{
Collection<Callback<Channel>> cancelled = new ArrayList<Callback<Channel>>();
final Collection<AsyncPool<Channel>> pools;
synchronized (_mutex)
{
pools = _pool.values();
}
for (AsyncPool<Channel> pool : pools)
{
cancelled.addAll(pool.cancelWaiters());
}
return cancelled;
}
public AsyncPool<Channel> getPoolForAddress(SocketAddress address) throws IllegalStateException
{
/*
Unsynchronized get is safe because this is a ConcurrentHashMap
We don't need to check whether we're shutting down, because each
pool maintains its own shutdown state. Synchronizing for get is
undesirable, because every request for every address comes through this path and it
would essentially be a global request lock.
*/
AsyncPool<Channel> pool = _pool.get(address);
if (pool != null)
{
return pool;
}
synchronized (_mutex)
{
if (_state != State.RUNNING)
{
throw new IllegalStateException("ChannelPoolManager is shutting down");
}
// Retry the get while synchronized
pool = _pool.get(address);
if (pool == null)
{
pool = _channelPoolFactory.getPool(address);
pool.start();
_pool.put(address, pool);
}
}
return pool;
}
/**
* Get statistics from each pool. The map keys represent pool names.
* The values are the corresponding {@link AsyncPoolStats} objects.
*
* @return A map of pool names and statistics.
*/
@Override
public Map<String, PoolStats> getPoolStats()
{
final Map<String, PoolStats> stats = new HashMap<String, PoolStats>();
for(AsyncPool<Channel> pool : _pool.values())
{
stats.put(pool.getName(), pool.getStats());
}
return stats;
}
@Override
public String getName()
{
return _name;
}
}