/* * Galaxy * Copyright (c) 2012-2014, Parallel Universe Software Co. All rights reserved. * * This program and the accompanying materials are dual-licensed under * either the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation * * or (per the licensee's choosing) * * under the terms of the GNU Lesser General Public License version 3.0 * as published by the Free Software Foundation. */ package co.paralleluniverse.galaxy.netty; import co.paralleluniverse.common.monitoring.ThreadPoolExecutorMonitor; import co.paralleluniverse.galaxy.Cluster; import co.paralleluniverse.galaxy.core.ClusterService; import co.paralleluniverse.galaxy.core.CommThread; import co.paralleluniverse.galaxy.core.Message; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.*; import org.jboss.netty.channel.group.DefaultChannelGroup; import org.jboss.netty.channel.socket.nio.NioServerBossPool; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import org.jboss.netty.channel.socket.nio.NioWorkerPool; import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jmx.export.annotation.ManagedAttribute; import java.net.InetSocketAddress; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicLong; import static co.paralleluniverse.galaxy.netty.NettyUtils.KEEP_UNCHANGED_DETERMINER; /** * @author pron */ public abstract class AbstractTcpServer extends ClusterService { private final Logger LOG = LoggerFactory.getLogger(AbstractTcpServer.class.getName() + "." + getName()); // private final int port; private ChannelFactory channelFactory; private ServerBootstrap bootstrap; private final DefaultChannelGroup channels; private final AtomicLong nextMessageId = new AtomicLong(1L); private ChannelPipelineFactory origChannelFacotry; private final ChannelHandler testHandler; private ThreadPoolExecutor bossExecutor; private ThreadPoolExecutor workerExecutor; private OrderedMemoryAwareThreadPoolExecutor receiveExecutor; AbstractTcpServer(String name, final Cluster cluster, DefaultChannelGroup channels, int port, final ChannelHandler testHandler) { super(name, cluster); this.channels = channels; this.port = port; this.testHandler = testHandler; } public AbstractTcpServer(String name, Cluster cluster, DefaultChannelGroup channels, int port) { this(name, cluster, channels, port, null); } @Override protected void init() throws Exception { super.init(); if (bossExecutor == null) bossExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool(); if (workerExecutor == null) workerExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool(); final short currentNodeId = getCluster().getMyNodeId(); configureThreadPool(currentNodeId + "-" + getName() + "-tcpServerBoss", bossExecutor); configureThreadPool(currentNodeId + "-" + getName() + "-tcpServerWorker", workerExecutor); if (receiveExecutor != null) configureThreadPool(currentNodeId + "-" + getName() + "-tcpServerReceive", receiveExecutor); channelFactory = new NioServerSocketChannelFactory( new NioServerBossPool(bossExecutor, NettyUtils.DEFAULT_BOSS_COUNT, KEEP_UNCHANGED_DETERMINER), new NioWorkerPool(workerExecutor, NettyUtils.getWorkerCount(workerExecutor), KEEP_UNCHANGED_DETERMINER)); bootstrap = new ServerBootstrap(channelFactory); origChannelFacotry = new TcpMessagePipelineFactory(LOG, channels, receiveExecutor) { @Override public ChannelPipeline getPipeline() throws Exception { final ChannelPipeline pipeline = super.getPipeline(); pipeline.addBefore("messageCodec", "nodeNameReader", new ChannelNodeNameReader(getCluster())); pipeline.addLast("router", channelHandler); if (testHandler != null) pipeline.addLast("test", testHandler); return pipeline; } }; bootstrap.setPipelineFactory(new ChannelPipelineFactory() { @Override public ChannelPipeline getPipeline() throws Exception { return AbstractTcpServer.this.getPipeline(); } }); //bootstrap.setParentHandler(new LoggingHandler(LOG)); bootstrap.setOption("reuseAddress", true); bootstrap.setOption("child.tcpNoDelay", true); bootstrap.setOption("child.keepAlive", true); } public void setBossExecutor(ThreadPoolExecutor bossExecutor) { assertDuringInitialization(); this.bossExecutor = bossExecutor; } public void setWorkerExecutor(ThreadPoolExecutor workerExecutor) { assertDuringInitialization(); this.workerExecutor = workerExecutor; } public void setReceiveExecutor(OrderedMemoryAwareThreadPoolExecutor receiveExecutor) { assertDuringInitialization(); this.receiveExecutor = receiveExecutor; } private void configureThreadPool(String name, ThreadPoolExecutor executor) { executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); executor.setThreadFactory(new ThreadFactoryBuilder().setNameFormat(name + "-%d").setThreadFactory(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new CommThread(r); } }).build()); ThreadPoolExecutorMonitor.register(name, executor); } private final ChannelHandler channelHandler = new SimpleChannelHandler() { @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { final Message message = (Message) e.getMessage(); LOG.debug("Received {}", message); receive(ctx, message); } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { LOG.info("Channel exception: {} {}", e.getCause().getClass().getName(), e.getCause().getMessage()); ctx.getChannel().close(); } }; protected ChannelPipeline getPipeline() throws Exception { return origChannelFacotry.getPipeline(); } abstract protected void receive(ChannelHandlerContext ctx, Message message); protected void bind() { Channel channel = bootstrap.bind(new InetSocketAddress(port)); channels.add(channel); LOG.info("Channel {} listening on port {}", channel, port); setReady(true); } @Override public void shutdown() { LOG.info("Shutting down."); channels.close().awaitUninterruptibly(); channelFactory.releaseExternalResources(); } protected DefaultChannelGroup getChannels() { return channels; } protected long nextMessageId() { return nextMessageId.getAndIncrement(); } @ManagedAttribute public int getPort() { return port; } }