com.zipwhip.api.signals.sockets.netty.ChannelWrapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of zipwhip-api Show documentation
Show all versions of zipwhip-api Show documentation
Java client to support applications powered by the Zipwhip Cloud
The newest version!
package com.zipwhip.api.signals.sockets.netty;
import com.zipwhip.api.signals.sockets.ConnectionHandle;
import com.zipwhip.api.signals.sockets.ConnectionState;
import com.zipwhip.api.signals.sockets.ConnectionStateManagerFactory;
import com.zipwhip.api.signals.sockets.netty.pipeline.SignalsChannelHandler;
import com.zipwhip.concurrent.FutureUtil;
import com.zipwhip.concurrent.ObservableFuture;
import com.zipwhip.executors.NamedThreadFactory;
import com.zipwhip.lifecycle.CascadingDestroyableBase;
import com.zipwhip.lifecycle.DestroyableBase;
import com.zipwhip.util.Asserts;
import com.zipwhip.util.StateManager;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.SocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Created with IntelliJ IDEA.
* User: Michael
* Date: 8/15/12
* Time: 2:47 PM
*
* This wrapper controls the ensureAbleTo to a channel. The concept is that 1 wrapper controls all ensureAbleTo to 1 channel.
* Since all requests to a channel must go through this wrapper, we can safely control the threading behavior.
*
* THIS WRAPPER IS NEVER ALLOWED TO SELF DESTRUCT!
*/
public class ChannelWrapper extends CascadingDestroyableBase {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelWrapper.class);
/**
* This delegate represents the channel's ensureAbleTo to our SignalConnectionBase class. The ChannelHandlers
* need to be able to talk into the SignalProvider 'safely'. If their connection gets torn down we need to
* terminate their ensureAbleTo to the provider.
*/
private SignalConnectionDelegate delegate;
/**
* This is the channel that we're trying to protect.
*/
public Channel channel;
/**
* This connection is our external representation of the connection. External entities can interact with it
* through this 'connection' object. It's very similar to the "delegate" though I was concerned about combining
* them since they have different purposes and ensureAbleTo.
*/
protected final ChannelWrapperConnectionHandle connection;
/**
* Independently keep track of the state of the connection.
*/
protected final StateManager stateManager;
protected final ExecutorService executor;
protected final SignalConnectionBase signalConnectionBase;
/**
* For keeping absolute care over the thread safe state
*/
public ChannelWrapper(long id, Channel channel, SignalConnectionBase signalConnectionBase, ExecutorService executor) {
this.channel = channel;
this.stateManager = ConnectionStateManagerFactory.newStateManager();
this.link(stateManager);
this.connection = new ChannelWrapperConnectionHandle(id, signalConnectionBase, this);
this.connection.link(this);
if (executor == null){
this.executor = Executors.newSingleThreadExecutor(new NamedThreadFactory("ChannelWrapper(newSingleThreadExecutor)-"));
this.link(new DestroyableBase() {
@Override
protected void onDestroy() {
ChannelWrapper.this.executor.shutdownNow();
}
});
} else {
this.executor = executor;
}
this.signalConnectionBase = signalConnectionBase;
}
/**
* Safely connect the underlying channel.
*
* @param remoteAddress The address to connect to.
* @throws InterruptedException if interrupted while connecting.
*/
public synchronized void connect(final SocketAddress remoteAddress) throws Throwable {
Asserts.assertTrue(!isDestroyed(), "I was destroyed?");
Asserts.assertTrue(channel != null, "Channel was destroyed?");
stateManager.transitionOrThrow(ConnectionState.CONNECTING);
Asserts.assertTrue(delegate == null, "Did this class get used twice?");
// do the connect async.
ChannelFuture future = null;
try {
LOGGER.debug(String.format("Connecting %s to %s", channel, remoteAddress));
future = channel.connect(remoteAddress);
// since we're on the IO thread, we need to block here.
future.await(signalConnectionBase.getConnectTimeoutSeconds(), TimeUnit.SECONDS);
} catch (Exception e) {
if (future == null){
// oh shit! it crashed!
} else if (!future.isDone()) {
future.cancel();
// Subsequent calls to close have no effect
future.getChannel().close();
}
stateManager.transitionOrThrow(ConnectionState.DISCONNECTED);
throw e;
}
if (future.isSuccess() && future.getChannel().isConnected()) {
setupConnectedChannel(channel);
stateManager.transitionOrThrow(ConnectionState.CONNECTED);
} else {
LOGGER.debug("Oh shit, it's not completed. We're going to cancel/close everything.");
// cancel it.
future.cancel();
// Subsequent calls to close have no effect
future.getChannel().close();
stateManager.transitionOrThrow(ConnectionState.DISCONNECTED);
if (future.getCause() != null) {
throw future.getCause();
} else {
throw new IllegalStateException(String.format("The future was not successful %s/%s/%s/%s", future.isDone(), future.isSuccess(), future.getCause(), future.getChannel()));
}
}
}
private void setupConnectedChannel(Channel channel) {
// the delegate lets the ChannelHandlers talk to the connection (such as pong-received)
this.delegate = new SignalConnectionDelegate(this.signalConnectionBase, this.connection);
// add the 'business logic' ChannelHandler to the pipeline
channel.getPipeline().addLast("nettyChannelHandler", new SignalsChannelHandler(this.delegate));
this.link(this.delegate);
}
/**
* Will execute the connection/disconnection on a the caller's thread.
* We will terminate the delegate before attempting to close the connection, so this means that
* NO netty disconnected events will be able to fire. So the caller should be sure to throw its own
* appropriate disconnect events rather than rely on the netty ones. NOTE: the netty ones are pretty unreliable
* anyway.
*/
public synchronized void disconnect() {
// make sure our state is correct
stateManager.transitionOrThrow(ConnectionState.DISCONNECTING);
// because we intend to delete this.channel during the 'ondestroy' method, we need to capture the
// reference so we don't NPE.
final Channel channel = this.channel;
LOGGER.debug("Closing channel " + channel);
// the delegate needs to know that it's been destroyed.
// the word 'destroy' here means that it is no longer the currently active channel.
// this is important to prevent stray requests from entering the queues from the network channel threads.
this.channel = null;
// before we actually close the channel, we need to tear down the link.
if (this.delegate != null)
this.delegate.pause();
try {
try {
// wait synchronously for it to close?
if (!channel.close().await(signalConnectionBase.getConnectTimeoutSeconds(), TimeUnit.SECONDS)){
// not completed in that time!
// what do we do?!?!
LOGGER.warn(String.format("The channel %s failed to close in the time limit! We are going to just destroy ourselves anyway.", channel));
// TODO: should we just say it's closed anyway??
stateManager.transitionOrThrow(ConnectionState.DISCONNECTED);
} else {
LOGGER.debug(String.format("The channel %s closed cleanly", channel));
stateManager.transitionOrThrow(ConnectionState.DISCONNECTED);
// for debugging (do after all the destroys took place so we aren't left in a bad state after the exception)
assertClosed(channel);
}
} catch (Exception e) {
LOGGER.error("Got exception trying to close", e);
// it was interrupted, do we call this disconnected??
stateManager.transitionOrThrow(ConnectionState.DISCONNECTED);
}
} catch (Exception e){
LOGGER.error("Crazy, we got an error the catch block of an error! ", e);
}
}
public synchronized ObservableFuture write(Object message) {
// need to ensure that the CONNECTED state doesn't change.
stateManager.ensure(ConnectionState.CONNECTED);
return FutureUtil.execute(executor, null,
new WriteOnChannelSafelyCallable(this, message));
}
private void assertClosed(Channel channel) {
// double check that it worked?
if (channel.isConnected()) {
throw new IllegalStateException(String.format("We assumed that Netty made isOpen, isConnected, isBound false! %b, %b, %b", channel.isOpen(), channel.isConnected(), channel.isBound()));
}
}
public ConnectionHandle getConnection() {
return connection;
}
@Override
protected void onDestroy() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Destroying ChannelWrapper %s / %s", this.channel, Thread.currentThread().toString()));
}
// ref counting
this.channel = null;
// forcibly kill it.
this.delegate = null;
// dont null out the "connection" object because it would cause null pointers. It's linked so it will auto destroy.
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy