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 io.eguan.proto.net.MsgWrapper; import java.net.InetSocketAddress; import java.util.Objects; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nonnull; import javax.management.InstanceAlreadyExistsException; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFactory; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.group.ChannelGroup; import org.jboss.netty.channel.group.DefaultChannelGroup; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import org.jboss.netty.handler.codec.protobuf.ProtobufDecoder; import org.jboss.netty.handler.codec.protobuf.ProtobufEncoder; import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; import org.jboss.netty.handler.execution.ExecutionHandler; import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; import org.jboss.netty.util.ThreadNameDeterminer; import org.jboss.netty.util.ThreadRenamingRunnable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.protobuf.MessageLite; /** * Message server endpoint which accepts connections from remote peers. * * @author oodrive * @author ebredzinski * @author llambert * */ public final class MsgServerEndpoint implements MsgServerMXBean { static final Logger LOGGER = LoggerFactory.getLogger(MsgServerEndpoint.class.getName()); /** Keep thread factories names */ static { ThreadRenamingRunnable.setThreadNameDeterminer(ThreadNameDeterminer.CURRENT); } /* * Constants used to configure the execution handler. */ private static final int NB_THREADS = 10; private static final int TIMEOUT_THREAD = 100; // ms private static final int MEMORY_CHANNEL = 0; // disabled private static final int MEMORY_GLOBAL = 0; // disabled /** * Factory used to create a pipeline for each channel. * * */ private static final class MsgServerPipelineFactory implements ChannelPipelineFactory { private final UUID msgServerId; private final ExecutionHandler executionHandler; private final MsgServerHandler msgServerHandler; private final MessageLite prototype; private final ChannelGroup channelGroup; private final AtomicBoolean serverStarted; MsgServerPipelineFactory(final UUID msgServerId, final ExecutionHandler executionHandler, final MsgServerHandler msgServerHandler, final MessageLite prototype, final ChannelGroup channelGroup, final AtomicBoolean serverStarted) { this.msgServerId = msgServerId; this.executionHandler = executionHandler; this.msgServerHandler = msgServerHandler; this.prototype = prototype; this.channelGroup = channelGroup; this.serverStarted = serverStarted; } @Override public ChannelPipeline getPipeline() throws Exception { final ChannelPipeline pipeline = Channels.pipeline(); pipeline.addLast("frameDecoder", new ProtobufVarint32FrameDecoder()); pipeline.addLast("protobufDecoder", new ProtobufDecoder(MsgWrapper.MsgRequest.getDefaultInstance())); pipeline.addLast("frameEncoder", new ProtobufVarint32LengthFieldPrepender()); pipeline.addLast("protobufEncoder", new ProtobufEncoder()); pipeline.addLast("executor handler", executionHandler); pipeline.addLast("application logic", new MsgServerGenericHandler(msgServerId, msgServerHandler, prototype, channelGroup, serverStarted)); return pipeline; } } private final AtomicBoolean serverStarted = new AtomicBoolean(false); /** The object which actually do the business logic of the application. */ private final MsgServerHandler msgServerHandler; /** Prototype of Protobuf messages. */ private final MessageLite prototype; /** Bind socket address. */ private final InetSocketAddress bindSocketAddress; /** Unique id of the message server. */ private final UUID msgServerId; /** A set which contains all remote channels. */ private final ChannelGroup channelGroup = new DefaultChannelGroup("all channels"); /** The Netty helper used to create the server channel. */ private ExecutorService bossWorker; private ExecutorService slaveWorker; private ServerBootstrap bootstrap; /** * The Netty's workers are relieved through the use of an execution handler which executes the * {@link MsgServerHandler}. */ private ExecutionHandler executionHandler; /** Thread factory to define thread name */ private final ThreadFactory nettyThreadFactory; /** * Constructor of a message server endpoint. * * @param self * identity and bind address of this node * @param msgServerHandler * The application logic to execute. * @param prototype * The Protobuf prototype used to deserialize the received messages */ public MsgServerEndpoint(@Nonnull final MsgNode self, @Nonnull final MsgServerHandler msgServerHandler, @Nonnull final MessageLite prototype) { this.msgServerHandler = Objects.requireNonNull(msgServerHandler); this.prototype = Objects.requireNonNull(prototype); this.bindSocketAddress = self.getAddress(); this.msgServerId = self.getNodeId(); this.nettyThreadFactory = new NetThreadFactory("MsgSrv[" + bindSocketAddress + "]-"); } /** * Start the server, after the call the server is ready to accept connections from remote peers. */ public final synchronized void start() { if (serverStarted.getAndSet(true)) { throw new IllegalStateException("Msg server already started"); } try { bossWorker = Executors.newCachedThreadPool(new NetThreadFactory("MsgSrv[" + bindSocketAddress + "]-b-")); slaveWorker = Executors.newCachedThreadPool(new NetThreadFactory("MsgSrv[" + bindSocketAddress + "]-w-")); final ChannelFactory factory = new NioServerSocketChannelFactory(bossWorker, slaveWorker, Runtime .getRuntime().availableProcessors() * 2); bootstrap = new ServerBootstrap(factory); executionHandler = new ExecutionHandler(new OrderedMemoryAwareThreadPoolExecutor(NB_THREADS, MEMORY_CHANNEL, MEMORY_GLOBAL, TIMEOUT_THREAD, TimeUnit.MILLISECONDS, nettyThreadFactory)); bootstrap.setPipelineFactory(new MsgServerPipelineFactory(msgServerId, executionHandler, msgServerHandler, prototype, channelGroup, serverStarted)); bootstrap.setOption("child.tcpNoDelay", Boolean.TRUE); bootstrap.setOption("child.keepAlive", Boolean.TRUE); bootstrap.setOption("child.reuseAddress", Boolean.TRUE); final Channel serverChannel = bootstrap.bind(bindSocketAddress); channelGroup.add(serverChannel); LOGGER.info( "Msg server [{}] started at {}:{}", new Object[] { msgServerId, bindSocketAddress.getHostString(), Integer.valueOf(bindSocketAddress.getPort()) }); } catch (final Throwable t) { LOGGER.error("Error while starting the message server [{}] at {}:{}, {}", new Object[] { msgServerId, bindSocketAddress.getHostString(), Integer.valueOf(bindSocketAddress.getPort()), t.toString() }); serverStarted.set(false); } } /** * Stop the server, every allocated resources are released, any connections from remote peers are not accepted. */ public final synchronized void stop() { if (!serverStarted.getAndSet(false)) { return; } try { channelGroup.close().awaitUninterruptibly(); channelGroup.clear(); bootstrap.releaseExternalResources(); bootstrap = null; executionHandler.releaseExternalResources(); executionHandler = null; LOGGER.info("Msg server [{}] stopped...", msgServerId); } catch (final Throwable t) { LOGGER.error("Error while stopping the message server [{}]", t, msgServerId); } } /** * Gets server {@link UUID}. * * @return the string of the {@link UUID}. */ @Override public final String getUuid() { return msgServerId.toString(); } /** * Gets the server IP address. * * @return the read-only IP address. */ @Override public final String getIpAddress() { return bindSocketAddress.getAddress().getHostAddress(); } /** * Gets the server port. * * @return the read-only port. */ @Override public final int getPort() { return bindSocketAddress.getPort(); } /** * Tells if the server is started. * * @return <code>true</code> if started. */ @Override public final boolean isStarted() { return serverStarted.get(); } /** * Restart the server. * */ @Override public final void restart() { stop(); start(); } /** * Register the end point MXBean. * * @throws InstanceAlreadyExistsException * @throws MBeanRegistrationException * @throws NotCompliantMBeanException * @throws MalformedObjectNameException * * @return {@link ObjectName} of the MXBean * */ public final ObjectName registerMXBean(final MBeanServer mbeanServer) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException { final ObjectName serverObjName = new ObjectName(this.getClass().getPackage().getName() + ":type=Server"); mbeanServer.registerMBean(this, serverObjName); return serverObjName; } }