package io.eguan.net;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
import org.jboss.netty.channel.Channel;
/**
* Stores the channels created to send long lasting requests to peers. The channels are closed automatically after a
* while.
*
* @author oodrive
* @author llambert
*
*/
final class MsgClientSpareChannels {
/**
* Channel connected to a peer node.
*
*/
interface PeerChannel {
/**
* {@link UUID} of the peer.
*
* @return the {@link UUID} of the peer.
*/
UUID getNode();
/**
* A {@link Channel} connected to the peer.
*
* @return an opened {@link Channel} to the peer.
*/
Channel getChannel();
}
/**
* New channel and node.
*
*/
private final class PeerChannelImpl implements PeerChannel {
private final UUID node;
private final Channel channel;
/** <code>true</code> when the close of the channel is done or in progress */
@GuardedBy(value = "this")
private boolean closed;
PeerChannelImpl(final UUID node, final Channel channel) {
super();
this.node = node;
this.channel = channel;
this.closed = false;
}
@Override
public final UUID getNode() {
return node;
}
@Override
public final Channel getChannel() {
return channel;
}
final synchronized void enqueue() {
if (!closed && channel.isConnected()) {
spareChannels.add(new PeerChannelRef(this));
}
}
/**
* Remove this channel from the spare ones.
*
* @return <code>true</code> if the channel is still connected
*/
final synchronized boolean dequeue(final PeerChannelRef peerChannelRef) {
if (!closed && channel.isConnected()) {
final PeerChannelImpl peerChannelImpl = peerChannelRef.removePeerChannel();
assert peerChannelImpl == this || peerChannelImpl == null;
// May have been removed by someone else?
return peerChannelImpl != null;
}
return false;
}
final synchronized void close() {
if (!closed) {
channel.close();
closed = true;
}
}
}
/**
* Object queued that reference a PeerChannel. The reference is <code>null</code> if the channel have been re-used.
*
*/
private final class PeerChannelRef implements Delayed {
private final AtomicReference<PeerChannelImpl> peerChannelRef;
/** Date of expiration, in seconds */
private final long timeEnd;
PeerChannelRef(final PeerChannelImpl peerChannelImpl) {
super();
this.peerChannelRef = new AtomicReference<>(peerChannelImpl);
this.timeEnd = System.currentTimeMillis() + delay;
}
final PeerChannelImpl getPeerChannel() {
return peerChannelRef.get();
}
final PeerChannelImpl removePeerChannel() {
return peerChannelRef.getAndSet(null);
}
@Override
public final int compareTo(final Delayed o) {
if (this == o) {
return 0;
}
final PeerChannelRef other = (PeerChannelRef) o;
final long endOther = other.timeEnd;
return (int) (timeEnd - endOther);
}
@Override
public final long getDelay(final TimeUnit unit) {
return unit.convert(timeEnd - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
}
/**
* Closes the spare channels.
*
*
*/
final class PeerChannelCloser implements Runnable {
private final AtomicBoolean shutdown;
PeerChannelCloser() {
super();
this.shutdown = new AtomicBoolean();
}
final void shutdown() {
shutdown.set(true);
}
@Override
public final void run() {
shutdown.set(false);
while (!shutdown.get()) {
try {
final PeerChannelRef toClose = spareChannels.poll(2, TimeUnit.SECONDS);
if (toClose != null) {
final PeerChannelImpl peerChannelImpl = toClose.removePeerChannel();
if (peerChannelImpl != null) {
peerChannelImpl.close();
}
}
}
catch (final Throwable t) {
MsgClientStartpoint.LOGGER.warn("Error while handling spare channels", t);
}
}
}
}
private static final long POLL = 2; // 2 seconds
private final MsgClientStartpoint clientStartpoint;
/** Name of the closer thread. */
private final String closerName;
/** Spare channels/ */
private final DelayQueue<PeerChannelRef> spareChannels = new DelayQueue<>();
private final PeerChannelCloser peerChannelCloser;
/** Thread closing channels */
private Thread closer;
private final long delay;
/**
* Can change delay for unit tests purpose.
*
* @param clientStartpoint
* @param delay
*/
MsgClientSpareChannels(final MsgClientStartpoint clientStartpoint, final long delay) {
super();
this.clientStartpoint = clientStartpoint;
this.closerName = "SpareChannels[" + clientStartpoint.getMsgClientId() + "]";
this.peerChannelCloser = new PeerChannelCloser();
this.delay = delay;
}
/**
* Start management of spare channels.
*/
final void start() {
closer = new Thread(peerChannelCloser, closerName);
try {
closer.setDaemon(true);
}
finally {
closer.start();
}
}
/**
* Release resources related to spare channels.
*/
final void stop() {
// First, shutdown thread
try {
final Thread closerTmp = closer;
closer = null;
if (closerTmp != null) {
peerChannelCloser.shutdown();
closerTmp.join(POLL * 1000);
}
}
catch (final Throwable t) {
MsgClientStartpoint.LOGGER.warn("Error while stopping " + closerName);
}
// Close the remaining channels
final Iterator<PeerChannelRef> ite = spareChannels.iterator();
while (ite.hasNext()) {
final PeerChannelRef peerChannelRef = ite.next();
final PeerChannelImpl peerChannelImpl = peerChannelRef.removePeerChannel();
if (peerChannelImpl != null) {
try {
peerChannelImpl.close();
}
catch (final Throwable t) {
MsgClientStartpoint.LOGGER.warn("Error while closing connection to " + peerChannelImpl.getNode());
}
}
}
spareChannels.clear();
}
/**
* Gets a channel connected to the given peer.
*
* @param node
* peer to connect to
* @return the Channel and peer ID.
* @throws InterruptedException
* @throws ConnectException
*/
final PeerChannel getChannel(@Nonnull final UUID node, @Nonnull final InetSocketAddress nodeAddr)
throws ConnectException, InterruptedException {
Objects.requireNonNull(node);
Objects.requireNonNull(nodeAddr);
// Look for a spare channel
final Iterator<PeerChannelRef> ite = spareChannels.iterator();
while (ite.hasNext()) {
final PeerChannelRef peerChannelRef = ite.next();
final PeerChannelImpl peerChannelImpl = peerChannelRef.getPeerChannel();
if (peerChannelImpl != null && peerChannelImpl.getNode().equals(node)) {
if (peerChannelImpl.dequeue(peerChannelRef)) {
assert peerChannelRef.getPeerChannel() == null;
assert peerChannelImpl.getChannel().isConnected();
return peerChannelImpl;
}
}
}
// Open a new channel
final Channel peerChannel = clientStartpoint.newSecondaryChannelFuture(nodeAddr);
return new PeerChannelImpl(node, peerChannel);
}
/**
* Release a {@link PeerChannel}, making it available for a future use.
*
* @param peerChannel
*/
final void releaseChannel(final PeerChannel peerChannel) {
((PeerChannelImpl) peerChannel).enqueue();
}
/**
* For unit tests.
*
* @return the number of opened channels.
*/
final int getChannelCount() {
int count = 0;
final Iterator<PeerChannelRef> ite = spareChannels.iterator();
while (ite.hasNext()) {
final PeerChannelRef peerChannelRef = ite.next();
final PeerChannelImpl peerChannelImpl = peerChannelRef.getPeerChannel();
if (peerChannelImpl != null) {
count++;
}
}
return count;
}
}