
cn.ipokerface.aps.channel.ChannelPool Maven / Gradle / Ivy
Show all versions of apple-pns-java Show documentation
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;
}
}