// =================================================================================================
// Copyright 2011 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.net.pool;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.twitter.common.base.Closure;
import com.twitter.common.net.loadbalancing.LoadBalancer;
import com.twitter.common.net.pool.DynamicHostSet.MonitorException;
import com.twitter.common.quantity.Amount;
import com.twitter.common.quantity.Time;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;
/**
* An ObjectPool that maintains a set of connections for a set of service endpoints defined by a
* {@link com.twitter.common.zookeeper.ServerSet}.
*
* @param <H> The type that contains metadata information about hosts, such as liveness and address.
* @param <T> The raw connection type that is being pooled.
* @param <E> The type that identifies the endpoint of the pool, such as an address.
* @author John Sirois
*/
public class DynamicPool<H, T, E> implements ObjectPool<Connection<T, E>> {
private final MetaPool<T, E> pool;
/**
* Creates a new ServerSetConnectionPool and blocks on an initial read and constructions of pools
* for the given {@code serverSet}.
*
* @param hostSet the dynamic set of available servers to pool connections for
* @param endpointPoolFactory a factory that can generate a connection pool for an endpoint
* @param loadBalancer Load balancer to manage request flow.
* @param onBackendsChosen A callback to notify of chosen backends.
* @param restoreInterval the interval after connection errors start occurring for a target to
* begin checking to see if it has come back to a healthy state
* @param endpointExtractor Function that transforms a service instance into an endpoint instance.
* @param livenessChecker Filter that will determine whether a host indicates itself as available.
* @throws MonitorException if there is a problem monitoring the host set
*/
public DynamicPool(DynamicHostSet<H> hostSet,
Function<E, ObjectPool<Connection<T, E>>> endpointPoolFactory,
LoadBalancer<E> loadBalancer,
Closure<Collection<E>> onBackendsChosen,
Amount<Long, Time> restoreInterval,
Function<H, E> endpointExtractor,
Predicate<H> livenessChecker)
throws DynamicHostSet.MonitorException {
Preconditions.checkNotNull(hostSet);
Preconditions.checkNotNull(endpointPoolFactory);
pool = new MetaPool<T, E>(loadBalancer, onBackendsChosen, restoreInterval);
// TODO(John Sirois): consider an explicit start/stop
hostSet.monitor(new PoolMonitor<H, Connection<T, E>>(endpointPoolFactory, endpointExtractor,
livenessChecker) {
@Override protected void onPoolRebuilt(Set<ObjectPool<Connection<T, E>>> deadPools,
Map<E, ObjectPool<Connection<T, E>>> livePools) {
poolRebuilt(deadPools, livePools);
}
});
}
@VisibleForTesting
void poolRebuilt(Set<ObjectPool<Connection<T, E>>> deadPools,
Map<E, ObjectPool<Connection<T, E>>> livePools) {
pool.setBackends(livePools);
for (ObjectPool<Connection<T, E>> deadTargetPool : deadPools) {
deadTargetPool.close();
}
}
@Override
public Connection<T, E> get() throws ResourceExhaustedException, TimeoutException {
return pool.get();
}
@Override
public Connection<T, E> get(Amount<Long, Time> timeout)
throws ResourceExhaustedException, TimeoutException {
return pool.get(timeout);
}
@Override
public void release(Connection<T, E> connection) {
pool.release(connection);
}
@Override
public void remove(Connection<T, E> connection) {
pool.remove(connection);
}
@Override
public void close() {
pool.close();
}
private abstract class PoolMonitor<H, S extends Connection<?, ?>>
implements DynamicHostSet.HostChangeMonitor<H> {
private final Function<E, ObjectPool<S>> endpointPoolFactory;
private final Function<H, E> endpointExtractor;
private final Predicate<H> livenessTest;
public PoolMonitor(Function<E, ObjectPool<S>> endpointPoolFactory,
Function<H, E> endpointExtractor,
Predicate<H> livenessTest) {
this.endpointPoolFactory = endpointPoolFactory;
this.endpointExtractor = endpointExtractor;
this.livenessTest = livenessTest;
}
private final Map<E, ObjectPool<S>> endpointPools = Maps.newHashMap();
@Override
public synchronized void onChange(ImmutableSet<H> serverSet) {
// TODO(John Sirois): change onChange to pass the delta data since its already computed by
// ServerSet
Map<E, H> newEndpoints =
Maps.uniqueIndex(Iterables.filter(serverSet, livenessTest), endpointExtractor);
Set<E> deadEndpoints = ImmutableSet.copyOf(
Sets.difference(endpointPools.keySet(), newEndpoints.keySet()));
Set<ObjectPool<S>> deadPools = Sets.newHashSet();
for (E endpoint : deadEndpoints) {
ObjectPool<S> deadPool = endpointPools.remove(endpoint);
deadPools.add(deadPool);
}
Set<E> addedEndpoints = ImmutableSet.copyOf(
Sets.difference(newEndpoints.keySet(), endpointPools.keySet()));
for (E endpoint : addedEndpoints) {
ObjectPool<S> endpointPool = endpointPoolFactory.apply(endpoint);
endpointPools.put(endpoint, endpointPool);
}
onPoolRebuilt(deadPools, ImmutableMap.copyOf(endpointPools));
}
protected abstract void onPoolRebuilt(Set<ObjectPool<S>> deadPools,
Map<E, ObjectPool<S>> livePools);
}
}