/*
* Hivemall: Hive scalable Machine Learning Library
*
* Copyright (C) 2013-2014
* National Institute of Advanced Industrial Science and Technology (AIST)
* Registration Number: H25PRO-1520
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package hivemall.mix.server;
import hivemall.mix.metrics.MetricsRegistry;
import hivemall.mix.metrics.MixServerMetrics;
import hivemall.mix.metrics.ThroughputCounter;
import hivemall.mix.store.SessionStore;
import hivemall.mix.store.SessionStore.IdleSessionSweeper;
import hivemall.utils.lang.CommandLineUtils;
import hivemall.utils.lang.Primitives;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import java.security.cert.CertificateException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.net.ssl.SSLException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
public final class MixServer implements Runnable {
public static final int DEFAULT_PORT = 11212;
private final int port;
private final boolean ssl;
private final float scale;
private final short syncThreshold;
private final long sessionTTLinSec;
private final long sweepIntervalInSec;
private final boolean jmx;
private volatile ServerState state;
public MixServer(CommandLine cl) {
this.port = Primitives.parseInt(cl.getOptionValue("port"), DEFAULT_PORT);
this.ssl = cl.hasOption("ssl");
this.scale = Primitives.parseFloat(cl.getOptionValue("scale"), 1.f);
this.syncThreshold = Primitives.parseShort(cl.getOptionValue("sync"), (short) 30);
this.sessionTTLinSec = Primitives.parseLong(cl.getOptionValue("ttl"), 120L);
this.sweepIntervalInSec = Primitives.parseLong(cl.getOptionValue("sweep"), 60L);
this.jmx = cl.hasOption("jmx");
this.state = ServerState.INITIALIZING;
}
public static void main(String[] args) {
Options opts = getOptions();
CommandLine cl = CommandLineUtils.parseOptions(args, opts);
new MixServer(cl).run();
}
static Options getOptions() {
Options opts = new Options();
opts.addOption("p", "port", true, "port number of the mix server [default: 11212]");
opts.addOption("ssl", false, "Use SSL for the mix communication [default: false]");
opts.addOption("scale", "scalemodel", true, "Scale values of prediction models to avoid overflow [default: 1.0 (no-scale)]");
opts.addOption("sync", "sync_threshold", true, "Synchronization threshold using clock difference [default: 30]");
opts.addOption("ttl", "session_ttl", true, "The TTL in sec that an idle session lives [default: 120 sec]");
opts.addOption("sweep", "session_sweep_interval", true, "The interval in sec that the session expiry thread runs [default: 60 sec]");
opts.addOption("jmx", "metrics", false, "Toggle this option to enable monitoring metrics using JMX [default: false]");
return opts;
}
public ServerState getState() {
return state;
}
@Override
public void run() {
try {
start();
} catch (CertificateException e) {
e.printStackTrace();
} catch (SSLException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void start() throws CertificateException, SSLException, InterruptedException {
// Configure SSL.
final SslContext sslCtx;
if(ssl) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey());
} else {
sslCtx = null;
}
// configure metrics
ScheduledExecutorService metricCollector = Executors.newScheduledThreadPool(1);
MixServerMetrics metrics = new MixServerMetrics();
ThroughputCounter throughputCounter = new ThroughputCounter(metricCollector, 5000L, metrics);
if(jmx) {// register mbean
MetricsRegistry.registerMBeans(metrics, port);
}
// configure initializer
SessionStore sessionStore = new SessionStore();
MixServerHandler msgHandler = new MixServerHandler(sessionStore, syncThreshold, scale);
MixServerInitializer initializer = new MixServerInitializer(msgHandler, throughputCounter, sslCtx);
Runnable cleanSessionTask = new IdleSessionSweeper(sessionStore, sessionTTLinSec * 1000L);
ScheduledExecutorService idleSessionChecker = Executors.newScheduledThreadPool(1);
try {
// start idle session sweeper
idleSessionChecker.scheduleAtFixedRate(cleanSessionTask, sessionTTLinSec + 10L, sweepIntervalInSec, TimeUnit.SECONDS);
// accept connections
acceptConnections(initializer, port);
} finally {
// release threads
idleSessionChecker.shutdownNow();
if(jmx) {
MetricsRegistry.unregisterMBeans(port);
}
metricCollector.shutdownNow();
}
}
private void acceptConnections(@Nonnull MixServerInitializer initializer, int port)
throws InterruptedException {
final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
final EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.option(ChannelOption.SO_KEEPALIVE, true);
b.group(bossGroup, workerGroup);
b.channel(NioServerSocketChannel.class);
b.handler(new LoggingHandler(LogLevel.INFO));
b.childHandler(initializer);
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync();
this.state = ServerState.RUNNING;
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
this.state = ServerState.STOPPING;
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public enum ServerState {
INITIALIZING, RUNNING, STOPPING,
}
}