package com.dianping.pigeon.remoting.common.pool; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import com.dianping.pigeon.log.Logger; import com.dianping.pigeon.log.LoggerLoader; import com.dianping.pigeon.remoting.common.channel.Channel; import com.dianping.pigeon.remoting.common.channel.ChannelFactory; import com.dianping.pigeon.remoting.common.exception.NetworkException; import com.dianping.pigeon.threadpool.DefaultThreadFactory; import com.dianping.pigeon.util.AtomicPositiveInteger; /** * @author qi.yin 2016/09/23 上午10:52. */ public class DefaultChannelPool<C extends Channel> implements ChannelPool<C> { private static final Logger logger = LoggerLoader.getLogger(DefaultChannelPool.class); private final List<C> pooledChannels = new ArrayList<C>(); private AtomicInteger size = new AtomicInteger(); private AtomicPositiveInteger selectedIndex = new AtomicPositiveInteger(0); private AtomicBoolean isClosed = new AtomicBoolean(true); private PoolProperties properties; private ChannelFactory<C> channelFactory; private ScheduledFuture<?> scheduledFuture = null; private static ExecutorService reconnectExecutor = Executors.newFixedThreadPool(4, new DefaultThreadFactory("Pigeon-ChannelPool-Reconnect-Pool")); private static ScheduledThreadPoolExecutor checkScheduler = new ScheduledThreadPoolExecutor(2, new DefaultThreadFactory("Pigeon-ChannelPool-Check-Pool")); private final static Object PRESENT = new Object(); private static final ConcurrentMap<Channel, Object> reconnectChannels = new ConcurrentHashMap<Channel, Object>(); public DefaultChannelPool(ChannelFactory channelFactory) throws ChannelPoolException { this(new PoolProperties(), channelFactory); } public DefaultChannelPool(PoolProperties properties, ChannelFactory channelFactory) throws ChannelPoolException { if (isClosed.compareAndSet(true, false)) { this.properties = properties; this.channelFactory = channelFactory; init(properties); } } private void init(PoolProperties properties) throws ChannelPoolException { if (properties.getMaxActive() < 1) { logger.info( "[init] maxActive is smaller than 1, setting maxActive to " + PoolProperties.DEFAULT_MAX_ACTIVE); properties.setMaxActive(PoolProperties.DEFAULT_MAX_ACTIVE); } if (properties.getInitialSize() > properties.getMaxActive()) { logger.info( "[init] initialSize is larger than maxActive, setting initialSize to" + properties.getMaxActive()); properties.setInitialSize(properties.getMaxActive()); } if (properties.getNormalSize() > properties.getMaxActive()) { logger.info( "[init] normalSize is larger than maxActive, setting normalSize to" + properties.getMaxActive()); properties.setNormalSize(properties.getMaxActive()); } if (properties.getNormalSize() < properties.getInitialSize()) { logger.info("[init] normalSize is smaller than initialSize, setting normalSize to" + properties.getInitialSize()); properties.setNormalSize(properties.getInitialSize()); } try { for (int i = 0; i < properties.getInitialSize(); i++) { selectChannel(); } } catch (ChannelPoolException e) { logger.info("[init] unable to create initial connections of pool.", e); } initCheckScheduler(); } private void initCheckScheduler() { int interval = properties.getTimeBetweenCheckerMillis(); scheduledFuture = checkScheduler.scheduleWithFixedDelay(new CheckChannelTask(this), interval, interval, TimeUnit.MILLISECONDS); } @Override public int getSize() { return pooledChannels.size(); } @Override public boolean isAvaliable() { if (isClosed()) { return false; } for (int index = 0; index < pooledChannels.size(); index++) { C channel = pooledChannels.get(index); if (channel != null && channel.isAvaliable()) { return true; } } return false; } @Override public C selectChannel() throws ChannelPoolException { if (isClosed()) { throw new ChannelPoolException("Channel pool is closed."); } long now = System.nanoTime(); long maxWait = (properties.getMaxWait() < 0) ? Long.MAX_VALUE : properties.getMaxWait(); do { // create if (size.get() < properties.getNormalSize()) { if (size.incrementAndGet() > properties.getNormalSize()) { size.decrementAndGet(); } else { return createChannel(); } } // random if (!pooledChannels.isEmpty()) { int selected = selectedIndex.getAndIncrement() % pooledChannels.size(); C pooledChannel = pooledChannels.get(selected); if (pooledChannel != null) { if (!pooledChannel.isAvaliable()) { reconnectChannel(pooledChannel, this); } else { return pooledChannel; } } } // timeout if (((System.nanoTime() - now) / 1000000) >= maxWait) { throw new ChannelPoolException( "TimeOut:pool empty. Unable to fetch a channel, none avaliable in use." + getChannelPoolDesc()); } } while (true); } protected C createChannel() { C channel = null; try { channel = channelFactory.createChannel(); } finally { if (channel != null) { synchronized (pooledChannels) { pooledChannels.add(channel); } } else { size.decrementAndGet(); } } return channel; } protected static void reconnectChannel(Channel channel, ChannelPool channelPool) { if (reconnectChannels.putIfAbsent(channel, PRESENT) == null) { reconnectExecutor.submit(new ReconnectChannelTask(channel, channelPool)); } } @Override public List<C> getChannels() { return pooledChannels; } @Override public PoolProperties getPoolProperties() { return properties; } @Override public void close() { if (isClosed.compareAndSet(false, true)) { if (scheduledFuture != null && !scheduledFuture.isCancelled()) { scheduledFuture.cancel(true); checkScheduler.purge(); } scheduledFuture = null; for (int index = 0; index < pooledChannels.size(); index++) { C pooledChannel = pooledChannels.get(index); if (pooledChannel != null) { if (pooledChannel.isAvaliable()) { pooledChannel.disConnect(); } else { if (reconnectChannels.containsKey(pooledChannel)) { reconnectChannels.remove(pooledChannel); } } } } } } @Override public boolean isClosed() { return isClosed.get(); } protected String getChannelPoolDesc() { return "ChannelPool[poolSize=" + pooledChannels.size() + "]"; } static class ReconnectChannelTask implements Runnable { private WeakReference<Channel> channelRef; private WeakReference<ChannelPool> poolRef; public ReconnectChannelTask(Channel channel, ChannelPool pool) { this.channelRef = new WeakReference<Channel>(channel); this.poolRef = new WeakReference<ChannelPool>(pool); } @Override public void run() { ChannelPool channelPool = poolRef.get(); Channel channel = channelRef.get(); try { if (channelPool != null && !channelPool.isClosed()) { if (channel != null && !channel.isAvaliable()) { try { channel.connect(); } catch (NetworkException e) { logger.info("[run] pooledChannel connect failed. remoteAddress : " + channel.getRemoteAddressString()); } } } } finally { reconnectChannels.remove(channel); } } } static class CheckChannelTask implements Runnable { private WeakReference<ChannelPool> poolRef; public CheckChannelTask(ChannelPool channelPool) { this.poolRef = new WeakReference<ChannelPool>(channelPool); } @Override public void run() { try { ChannelPool channelPool = poolRef.get(); if (channelPool != null && !channelPool.isClosed()) { List<Channel> channels = channelPool.getChannels(); for (int index = 0; index < channels.size(); index++) { Channel channel = channels.get(index); if (!channel.isAvaliable()) { reconnectChannel(channel, channelPool); } } } } catch (Throwable t) { logger.info("[run] pooledChannel check failed.", t); } } } }