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

cn.ipokerface.aps.channel.ChannelPool Maven / Gradle / Ivy

The newest version!
package cn.ipokerface.aps.channel;

import cn.ipokerface.aps.PushClient;
import cn.ipokerface.aps.PushClientMetricsListener;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;

/**
 * Created by       PokerFace
 * Create Date      2020-08-24.
 * Email:           [email protected]
 * Version          1.0.0
 * 

* Description: */ public class ChannelPool { private Logger logger = LoggerFactory.getLogger(ChannelPool.class); private PushClient pushClient; /** * channel factory */ private ChannelFactory channelFactory; private int capacity; /** * */ private OrderedEventExecutor eventExecutor; private ChannelGroup channelGroup; private final Queue idleChannels = new ArrayDeque<>(); private final Set> pendingCreateChannelFutures = new HashSet<>(); private final Queue> pendingAcquisitionPromises = new ArrayDeque<>(); private boolean closed = false; /** * */ private PushClientMetricsListener listener; /** * Constructor of ChannelPool ... * * @param channelFactory * @param capacity * @param executor * @param listener */ public ChannelPool(PushClient client, ChannelFactory channelFactory, int capacity, OrderedEventExecutor executor, PushClientMetricsListener listener){ this.pushClient = client; this.channelFactory = channelFactory; this.capacity = capacity; this.eventExecutor = executor; this.listener = listener; this.channelGroup = new DefaultChannelGroup(this.eventExecutor, true); } /** *

Asynchronously acquires a channel from this channel pool. The acquired channel may be a pre-existing channel * stored in the pool or may be a new channel created on demand. If no channels are available and the pool is at * capacity, acquisition may be delayed until another caller releases a channel to the pool.

* *

When callers are done with a channel, they must release the channel back to the pool via the * {@link ChannelPool#release(Channel)} method.

* * @return a {@code Future} that will be notified when a channel is available * * @see ChannelPool#release(Channel) */ public Future acquire() { final Promise acquirePromise = new DefaultPromise<>(this.eventExecutor); if (this.eventExecutor.inEventLoop()) { this.acquireWithinEventExecutor(acquirePromise); } else { this.eventExecutor.submit(() -> acquireWithinEventExecutor(acquirePromise)).addListener(future -> { if (!future.isSuccess()) { acquirePromise.tryFailure(future.cause()); } }); } return acquirePromise; } private void acquireWithinEventExecutor(final Promise acquirePromise) { assert this.eventExecutor.inEventLoop(); if (!this.closed) { // We always want to open new channels if we have spare capacity. Once the pool is full, we'll start looking // for idle, pre-existing channels. if (this.channelGroup.size() + this.pendingCreateChannelFutures.size() < this.capacity) { final Future createChannelFuture = this.channelFactory.create(eventExecutor.newPromise()); this.pendingCreateChannelFutures.add(createChannelFuture); createChannelFuture.addListener((GenericFutureListener>) future -> { pendingCreateChannelFutures.remove(createChannelFuture); if (future.isSuccess()) { final Channel channel = future.getNow(); channelGroup.add(channel); if (listener != null) listener.handleConnectionAdded(pushClient); acquirePromise.trySuccess(channel); } else { if (listener != null) listener.handleConnectionCreationFailed(pushClient); acquirePromise.tryFailure(future.cause()); // If we failed to open a connection, this is the end of the line for this acquisition // attempt, and callers won't be able to release the channel (since they didn't get one // in the first place). Move on to the next acquisition attempt if one is present. handleNextAcquisition(); } }); } else { final Channel channelFromIdlePool = idleChannels.poll(); if (channelFromIdlePool != null) { if (channelFromIdlePool.isActive()) { acquirePromise.trySuccess(channelFromIdlePool); } else { // The channel from the idle pool isn't usable; discard it and create a new one instead this.discardChannel(channelFromIdlePool); this.acquireWithinEventExecutor(acquirePromise); } } else { // We don't have any connections ready to go, and don't have any more capacity to create new // channels. Add this acquisition to the queue waiting for channels to become available. pendingAcquisitionPromises.add(acquirePromise); } } } else { acquirePromise.tryFailure(new IllegalStateException("Channel pool has closed and no more channels may be acquired.")); } } /** * Returns a previously-acquired channel to the pool. * * @param channel the channel to return to the pool */ public void release(final Channel channel) { if (this.eventExecutor.inEventLoop()) { this.releaseWithinEventExecutor(channel); } else { this.eventExecutor.submit(() -> releaseWithinEventExecutor(channel)); } } private void releaseWithinEventExecutor(final Channel channel) { assert this.eventExecutor.inEventLoop(); this.idleChannels.add(channel); this.handleNextAcquisition(); } private void handleNextAcquisition() { assert this.eventExecutor.inEventLoop(); if (!this.pendingAcquisitionPromises.isEmpty()) { this.acquireWithinEventExecutor(this.pendingAcquisitionPromises.poll()); } } private void discardChannel(final Channel channel) { assert this.eventExecutor.inEventLoop(); this.idleChannels.remove(channel); this.channelGroup.remove(channel); if (listener != null) this.listener.handleConnectionRemoved(pushClient); this.channelFactory.destroy(channel, this.eventExecutor.newPromise()).addListener(destroyFuture -> { if (!destroyFuture.isSuccess()) { logger.warn("Failed to destroy channel.", destroyFuture.cause()); } }); } /** * Shuts down this channel pool and releases all retained resources. * * @return a {@code Future} that will be completed when all resources held by this pool have been released */ public Future close() { final Promise closePromise = new DefaultPromise<>(this.eventExecutor); this.channelGroup.close().addListener(allCloseFuture -> { closed = true; final Promise waitForPendingCreateChannelFuturesPromise = new DefaultPromise<>(eventExecutor); final PromiseCombiner combiner = new PromiseCombiner(eventExecutor); for (final Future f : pendingCreateChannelFutures) { combiner.add(f); } combiner.finish(waitForPendingCreateChannelFuturesPromise); waitForPendingCreateChannelFuturesPromise.addListener(pendingChannelsFuture -> { if (channelFactory instanceof Closeable) { ((Closeable) channelFactory).close(); } for (final Promise acquisitionPromise : pendingAcquisitionPromises) { acquisitionPromise.tryFailure(new IllegalStateException("Channel pool has closed and no more channels may be acquired.")); } closePromise.setSuccess(null); }); }); return closePromise; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy