package org.greencheek.elasticacheconfig.handler; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelHandler.Sharable; 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; import io.netty.handler.timeout.ReadTimeoutException; import org.greencheek.elasticacheconfig.client.ElastiCacheConfigServerChooser; import org.greencheek.elasticacheconfig.client.PeriodicConfigRetrievalClient; import org.greencheek.elasticacheconfig.confighandler.AsyncConfigInfoMessageHandler; import org.greencheek.elasticacheconfig.decoder.InvalidConfigVersionException; import org.greencheek.elasticacheconfig.domain.ConfigInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * Keep reconnecting to the server while printing out the current uptime and * connection attempt getStatus. */ @Sharable public class ClientInfoClientHandler extends SimpleChannelInboundHandler<ConfigInfo> { private static final Logger log = LoggerFactory.getLogger(ClientInfoClientHandler.class); private final RequestConfigInfoScheduler obtainConfigComand; private final AsyncConfigInfoMessageHandler asyncConfigInfoMessageHandler; private final TimeUnit reconnectTimeUnit; private final long reconnectDelay; private final TimeUnit idleReadTimeUnit; private final long idleReadTimeout; private final int connectionTimeoutInMillis; private final ElastiCacheConfigServerChooser configServerChooser; private final AtomicInteger invalidConsecutiveConfigs = new AtomicInteger(0); private final int maxConsecutiveInvalidConfigsBeforeReconnect; public ClientInfoClientHandler(RequestConfigInfoScheduler getConfigCommand, AsyncConfigInfoMessageHandler handler, TimeUnit reconnectTimeUnit,long reconnectDelay, TimeUnit idleReadTimeUnit, long idleReadTimeout, ElastiCacheConfigServerChooser configServerChooser, int noInvalidConfigsBeforeReconnect, int connectionTimeoutInMillis) { this.obtainConfigComand = getConfigCommand; this.asyncConfigInfoMessageHandler = handler; this.reconnectTimeUnit = reconnectTimeUnit; this.reconnectDelay = reconnectDelay; this.idleReadTimeUnit = idleReadTimeUnit; this.idleReadTimeout = idleReadTimeout; this.configServerChooser = configServerChooser; this.maxConsecutiveInvalidConfigsBeforeReconnect = noInvalidConfigsBeforeReconnect; this.connectionTimeoutInMillis = connectionTimeoutInMillis; } @Override public void channelActive(ChannelHandlerContext ctx) { log.info("Connected to: {}",ctx.channel().remoteAddress()); obtainConfigComand.schedule(ctx.executor(),ctx); } @Override public void channelRead0(ChannelHandlerContext ctx, ConfigInfo msg) throws Exception { asyncConfigInfoMessageHandler.processConfig(msg); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (!(evt instanceof IdleStateEvent)) { return; } IdleStateEvent e = (IdleStateEvent) evt; if (e.state() == IdleState.READER_IDLE) { // The connection was OK but there was no traffic for last period. log.info("Disconnecting due to no response on connection for config retrieval. A reconnect will occur."); ctx.close(); } else if(e.state() == IdleState.WRITER_IDLE) { // The connection was OK but there was no traffic for last period. log.info("Disconnecting due to no traffic on connection, either read or write. A reconnect will occur."); ctx.close(); } } @Override public void channelInactive(final ChannelHandlerContext ctx) { log.warn("Disconnected from: {}", ctx.channel().remoteAddress()); } @Override public void channelUnregistered(final ChannelHandlerContext ctx) throws Exception { if(log.isInfoEnabled()) { log.info("Sleeping for {}s before reconnect.", reconnectTimeUnit.toSeconds(reconnectDelay)); } final ClientInfoClientHandler handler = this; final EventLoop loop = ctx.channel().eventLoop(); loop.schedule(new Runnable() { @Override public void run() { log.info("Reconnecting"); PeriodicConfigRetrievalClient.configureBootstrap(configServerChooser, handler, new Bootstrap(), loop, idleReadTimeUnit,idleReadTimeout,connectionTimeoutInMillis); } }, reconnectDelay, reconnectTimeUnit); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if(cause instanceof InvalidConfigVersionException) { log.warn("invalid config supplied by elasticache"); if(maxConsecutiveInvalidConfigsBeforeReconnect==-1) return; if(invalidConsecutiveConfigs.incrementAndGet() >= maxConsecutiveInvalidConfigsBeforeReconnect) { invalidConsecutiveConfigs.set(0); ctx.close(); } } else { super.exceptionCaught(ctx, cause); ctx.close(); } } public void shutdown() { asyncConfigInfoMessageHandler.shutdown(); } }