package com.outbrain.gruffalo.netty; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.EventLoop; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; @ChannelHandler.Sharable public class GraphiteChannelInboundHandler extends SimpleChannelInboundHandler<String> { private static final Logger log = LoggerFactory.getLogger(GraphiteChannelInboundHandler.class); private static final int RECONNECT_DELAY_SEC = 5; private final GraphiteClient client; private final String graphiteTarget; private final Throttler throttler; private boolean serverReadEnabled = true; private final ChannelFutureListener restoreServerReads = new ChannelFutureListener() { @Override public void operationComplete(final ChannelFuture future) throws Exception { throttler.restoreClientReads(); } }; public GraphiteChannelInboundHandler(final GraphiteClient client, final String graphiteTarget, final Throttler throttler) { this.client = Preconditions.checkNotNull(client, "client may not be null"); this.graphiteTarget = graphiteTarget; this.throttler = Preconditions.checkNotNull(throttler, "throttler must not be null"); } @Override protected void channelRead0(final ChannelHandlerContext ctx, final String msg) throws Exception { log.warn("Got an unexpected downstream message: " + msg); } @Override public void channelActive(final ChannelHandlerContext ctx) throws Exception { log.info("Connected to: {}", ctx.channel().remoteAddress()); } @Override public void channelUnregistered(final ChannelHandlerContext ctx) throws Exception { log.warn("Got disconnected from {}... will try to reconnect in {} sec...", graphiteTarget, RECONNECT_DELAY_SEC); scheduleReconnect(ctx); } private void scheduleReconnect(final ChannelHandlerContext ctx) { final EventLoop loop = ctx.channel().eventLoop(); loop.schedule(new Runnable() { @Override public void run() { log.info("Reconnecting to {}", graphiteTarget); client.connect(); } }, RECONNECT_DELAY_SEC, TimeUnit.SECONDS); } @Override public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception { if (evt instanceof IdleStateEvent) { final IdleStateEvent e = (IdleStateEvent) evt; if (!serverReadEnabled && e.state() == IdleState.WRITER_IDLE) { // close the channel so we try to reconnect, and restore reads log.info("Outbound connection to {} seem disconnected. Closing channel and restoring server reads.", graphiteTarget); ctx.channel().close().addListener(restoreServerReads); } } } @Override public void channelWritabilityChanged(final ChannelHandlerContext ctx) throws Exception { final boolean autoread = ctx.channel().isWritable(); serverReadEnabled = autoread; if (!autoread) { client.onPushBack(); } throttler.changeServerAutoRead(autoread); } @Override public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception { log.error("Unexpected exception from downstream.", cause); ctx.close(); } }