/**
* Copyright 2016 LinkedIn Corp. All rights reserved.
*
* 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.
*/
package com.github.ambry.rest;
import com.github.ambry.config.NettyConfig;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Netty specific implementation of {@link NioServer}.
* <p/>
* Responsible for accepting connections from clients, decoding HTTP data and passing them on to services that can
* generate responses via {@link NettyMessageProcessor}.
* <p/>
* The accompanying framework also provides a Netty specific implementation of {@link RestResponseChannel}
* ({@link NettyResponseChannel}) for writing responses to clients.
* <p/>
* This implementation creates a pipeline of handlers for every connection that it accepts and the last inbound handler,
* {@link NettyMessageProcessor}, is responsible for processing the inbound requests and passing them to services that
* can generate a response.
*/
public class NettyServer implements NioServer {
private final NettyConfig nettyConfig;
private final NettyMetrics nettyMetrics;
private final Map<Integer, ChannelInitializer<SocketChannel>> channelInitializers;
private final Logger logger = LoggerFactory.getLogger(getClass());
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
/**
* Creates a new instance of NettyServer.
* @param nettyConfig the {@link NettyConfig} instance that defines the configuration parameters for the NettyServer.
* @param nettyMetrics the {@link NettyMetrics} instance to use to record metrics.
* @param channelInitializers a {@link Map} from port number to the {@link ChannelInitializer} used to initialize
* a new channel on that port.
*/
public NettyServer(NettyConfig nettyConfig, NettyMetrics nettyMetrics,
Map<Integer, ChannelInitializer<SocketChannel>> channelInitializers) {
this.nettyConfig = nettyConfig;
this.nettyMetrics = nettyMetrics;
this.channelInitializers = channelInitializers;
NettyRequest.bufferWatermark = nettyConfig.nettyServerRequestBufferWatermark;
logger.trace("Instantiated NettyServer");
}
@Override
public void start() throws InstantiationException {
long startupBeginTime = System.currentTimeMillis();
try {
logger.trace("Starting NettyServer deployment");
bossGroup = new NioEventLoopGroup(nettyConfig.nettyServerBossThreadCount);
workerGroup = new NioEventLoopGroup(nettyConfig.nettyServerWorkerThreadCount);
for (Map.Entry<Integer, ChannelInitializer<SocketChannel>> entry : channelInitializers.entrySet()) {
bindServer(entry.getKey(), entry.getValue(), bossGroup, workerGroup);
}
} catch (InterruptedException e) {
logger.error("NettyServer start await was interrupted", e);
nettyMetrics.nettyServerStartError.inc();
throw new InstantiationException(
"Netty server bind to port [" + nettyConfig.nettyServerPort + "] was interrupted");
} finally {
long startupTime = System.currentTimeMillis() - startupBeginTime;
logger.info("NettyServer start took {} ms", startupTime);
nettyMetrics.nettyServerStartTimeInMs.update(startupTime);
}
}
@Override
public void shutdown() {
logger.info("Shutting down NettyServer");
if (bossGroup != null && workerGroup != null && (!bossGroup.isTerminated() || !workerGroup.isTerminated())) {
long shutdownBeginTime = System.currentTimeMillis();
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
try {
if (!(workerGroup.awaitTermination(30, TimeUnit.SECONDS) && bossGroup.awaitTermination(30, TimeUnit.SECONDS))) {
logger.error("NettyServer shutdown failed after waiting for 30 seconds");
nettyMetrics.nettyServerShutdownError.inc();
}
} catch (InterruptedException e) {
logger.error("NettyServer termination await was interrupted. Shutdown may have been unsuccessful", e);
nettyMetrics.nettyServerShutdownError.inc();
} finally {
long shutdownTime = System.currentTimeMillis() - shutdownBeginTime;
logger.info("NettyServer shutdown took {} ms", shutdownTime);
nettyMetrics.nettyServerShutdownTimeInMs.update(shutdownTime);
}
}
}
/**
* Bootstrap a new server with a {@link ChannelInitializer} and bind it to a port.
* @param port the port number to bind this server to.
* @param channelInitializer the {@link ChannelInitializer} for request handling on this server.
* @param bossGroup the pool of boss threads that this server uses.
* @param workerGroup the pool of worker threads that this server uses.
* @throws InterruptedException if binding to the port failed.
*/
private void bindServer(int port, ChannelInitializer<SocketChannel> channelInitializer, EventLoopGroup bossGroup,
EventLoopGroup workerGroup) throws InterruptedException {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, nettyConfig.nettyServerSoBacklog)
.handler(new LoggingHandler(LogLevel.DEBUG))
.childHandler(channelInitializer);
b.bind(port).sync();
logger.info("NettyServer now listening on port {}", port);
}
}