/*
* Copyright 2014 NAVER Corp.
*
* 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.
*/
package com.navercorp.pinpoint.rpc.server;
import com.navercorp.pinpoint.common.util.PinpointThreadFactory;
import com.navercorp.pinpoint.rpc.PinpointSocket;
import com.navercorp.pinpoint.rpc.PinpointSocketException;
import com.navercorp.pinpoint.rpc.client.WriteFailFutureListener;
import com.navercorp.pinpoint.rpc.cluster.ClusterOption;
import com.navercorp.pinpoint.rpc.packet.PingPacket;
import com.navercorp.pinpoint.rpc.packet.ServerClosePacket;
import com.navercorp.pinpoint.rpc.server.handler.ServerStateChangeEventHandler;
import com.navercorp.pinpoint.rpc.stream.DisabledServerStreamChannelMessageListener;
import com.navercorp.pinpoint.rpc.stream.ServerStreamChannelMessageListener;
import com.navercorp.pinpoint.rpc.util.AssertUtils;
import com.navercorp.pinpoint.rpc.util.CpuUtils;
import com.navercorp.pinpoint.rpc.util.LoggerFactorySetup;
import com.navercorp.pinpoint.rpc.util.TimerFactory;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.ChannelGroupFutureListener;
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.util.ThreadNameDeterminer;
import org.jboss.netty.util.Timeout;
import org.jboss.netty.util.Timer;
import org.jboss.netty.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* @author Taejin Koo
*/
public class PinpointServerAcceptor implements PinpointServerConfig {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private static final long DEFAULT_TIMEOUTMILLIS = 3 * 1000;
private static final int WORKER_COUNT = CpuUtils.workerCount();
private volatile boolean released;
private ServerBootstrap bootstrap;
private InetAddress[] ignoreAddressList;
private Channel serverChannel;
private final ChannelGroup channelGroup = new DefaultChannelGroup("PinpointServerFactory");
private final PinpointServerChannelHandler nettyChannelHandler = new PinpointServerChannelHandler();
private ServerMessageListener messageListener = SimpleServerMessageListener.SIMPLEX_INSTANCE;
private ServerStreamChannelMessageListener serverStreamChannelMessageListener = DisabledServerStreamChannelMessageListener.INSTANCE;
private List<ServerStateChangeEventHandler> stateChangeEventHandler = new ArrayList<ServerStateChangeEventHandler>();
private final Timer healthCheckTimer;
private final Timer requestManagerTimer;
private final ClusterOption clusterOption;
private long defaultRequestTimeout = DEFAULT_TIMEOUTMILLIS;
static {
LoggerFactorySetup.setupSlf4jLoggerFactory();
}
public PinpointServerAcceptor() {
this(ClusterOption.DISABLE_CLUSTER_OPTION);
}
public PinpointServerAcceptor(ClusterOption clusterOption) {
ServerBootstrap bootstrap = createBootStrap(1, WORKER_COUNT);
setOptions(bootstrap);
addPipeline(bootstrap);
this.bootstrap = bootstrap;
this.healthCheckTimer = TimerFactory.createHashedWheelTimer("PinpointServerSocket-HealthCheckTimer", 50, TimeUnit.MILLISECONDS, 512);
this.requestManagerTimer = TimerFactory.createHashedWheelTimer("PinpointServerSocket-RequestManager", 50, TimeUnit.MILLISECONDS, 512);
this.clusterOption = clusterOption;
}
private ServerBootstrap createBootStrap(int bossCount, int workerCount) {
// profiler, collector
ExecutorService boss = Executors.newCachedThreadPool(new PinpointThreadFactory("Pinpoint-Server-Boss"));
NioServerBossPool nioServerBossPool = new NioServerBossPool(boss, bossCount, ThreadNameDeterminer.CURRENT);
ExecutorService worker = Executors.newCachedThreadPool(new PinpointThreadFactory("Pinpoint-Server-Worker"));
NioWorkerPool nioWorkerPool = new NioWorkerPool(worker, workerCount, ThreadNameDeterminer.CURRENT);
NioServerSocketChannelFactory nioClientSocketChannelFactory = new NioServerSocketChannelFactory(nioServerBossPool, nioWorkerPool);
return new ServerBootstrap(nioClientSocketChannelFactory);
}
private void setOptions(ServerBootstrap bootstrap) {
// is read/write timeout necessary? don't need it because of NIO?
// write timeout should be set through additional interceptor. write
// timeout exists.
// tcp setting
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setOption("child.keepAlive", true);
// buffer setting
bootstrap.setOption("child.sendBufferSize", 1024 * 64);
bootstrap.setOption("child.receiveBufferSize", 1024 * 64);
// bootstrap.setOption("child.soLinger", 0);
}
private void addPipeline(ServerBootstrap bootstrap) {
ServerPipelineFactory serverPipelineFactory = new ServerPipelineFactory(nettyChannelHandler);
bootstrap.setPipelineFactory(serverPipelineFactory);
}
void setPipelineFactory(ChannelPipelineFactory channelPipelineFactory) {
if (channelPipelineFactory == null) {
throw new NullPointerException("channelPipelineFactory must not be null");
}
bootstrap.setPipelineFactory(channelPipelineFactory);
}
public void bind(String host, int port) throws PinpointSocketException {
InetSocketAddress bindAddress = new InetSocketAddress(host, port);
bind(bindAddress);
}
public void bind(InetSocketAddress bindAddress) throws PinpointSocketException {
if (released) {
return;
}
logger.info("bind() {}", bindAddress);
this.serverChannel = bootstrap.bind(bindAddress);
sendPing();
}
private DefaultPinpointServer createPinpointServer(Channel channel) {
DefaultPinpointServer pinpointServer = new DefaultPinpointServer(channel, this);
return pinpointServer;
}
@Override
public long getDefaultRequestTimeout() {
return defaultRequestTimeout;
}
public void setDefaultRequestTimeout(long defaultRequestTimeout) {
this.defaultRequestTimeout = defaultRequestTimeout;
}
private boolean isIgnoreAddress(Channel channel) {
if (ignoreAddressList == null) {
return false;
}
final InetSocketAddress remoteAddress = (InetSocketAddress) channel.getRemoteAddress();
if (remoteAddress == null) {
return false;
}
InetAddress address = remoteAddress.getAddress();
for (InetAddress ignore : ignoreAddressList) {
if (ignore.equals(address)) {
return true;
}
}
return false;
}
public void setIgnoreAddressList(InetAddress[] ignoreAddressList) {
AssertUtils.assertNotNull(ignoreAddressList, "ignoreAddressList must not be null");
this.ignoreAddressList = ignoreAddressList;
}
@Override
public ServerMessageListener getMessageListener() {
return messageListener;
}
public void setMessageListener(ServerMessageListener messageListener) {
AssertUtils.assertNotNull(messageListener, "messageListener must not be null");
this.messageListener = messageListener;
}
@Override
public List<ServerStateChangeEventHandler> getStateChangeEventHandlers() {
return stateChangeEventHandler;
}
public void addStateChangeEventHandler(ServerStateChangeEventHandler stateChangeEventHandler) {
AssertUtils.assertNotNull(stateChangeEventHandler, "stateChangeEventHandler must not be null");
this.stateChangeEventHandler.add(stateChangeEventHandler);
}
@Override
public ServerStreamChannelMessageListener getStreamMessageListener() {
return serverStreamChannelMessageListener;
}
public void setServerStreamChannelMessageListener(ServerStreamChannelMessageListener serverStreamChannelMessageListener) {
AssertUtils.assertNotNull(serverStreamChannelMessageListener, "serverStreamChannelMessageListener must not be null");
this.serverStreamChannelMessageListener = serverStreamChannelMessageListener;
}
@Override
public Timer getHealthCheckTimer() {
return healthCheckTimer;
}
@Override
public Timer getRequestManagerTimer() {
return requestManagerTimer;
}
@Override
public ClusterOption getClusterOption() {
return clusterOption;
}
private void sendPing() {
logger.debug("sendPing");
final TimerTask pintTask = new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
if (timeout.isCancelled()) {
newPingTimeout(this);
return;
}
final ChannelGroupFuture write = channelGroup.write(PingPacket.PING_PACKET);
if (logger.isWarnEnabled()) {
write.addListener(new ChannelGroupFutureListener() {
private final ChannelFutureListener listener = new WriteFailFutureListener(logger, "ping write fail", "ping write success");
@Override
public void operationComplete(ChannelGroupFuture future) throws Exception {
if (logger.isWarnEnabled()) {
for (ChannelFuture channelFuture : future) {
channelFuture.addListener(listener);
}
}
}
});
}
newPingTimeout(this);
}
};
newPingTimeout(pintTask);
}
private void newPingTimeout(TimerTask pintTask) {
try {
logger.debug("newPingTimeout");
healthCheckTimer.newTimeout(pintTask, 1000 * 60 * 5, TimeUnit.MILLISECONDS);
} catch (IllegalStateException e) {
// stop in case of timer stopped
logger.debug("timer stopped. Caused:{}", e.getMessage());
}
}
public void close() {
synchronized (this) {
if (released) {
return;
}
released = true;
}
healthCheckTimer.stop();
closePinpointServer();
if (serverChannel != null) {
ChannelFuture close = serverChannel.close();
close.awaitUninterruptibly(3000, TimeUnit.MILLISECONDS);
serverChannel = null;
}
if (bootstrap != null) {
bootstrap.releaseExternalResources();
bootstrap = null;
}
// clear the request first and remove timer
requestManagerTimer.stop();
}
private void closePinpointServer() {
for (Channel channel : channelGroup) {
DefaultPinpointServer pinpointServer = (DefaultPinpointServer) channel.getAttachment();
if (pinpointServer != null) {
pinpointServer.sendClosePacket();
}
}
}
public List<PinpointSocket> getWritableSocketList() {
List<PinpointSocket> pinpointServerList = new ArrayList<PinpointSocket>();
for (Channel channel : channelGroup) {
DefaultPinpointServer pinpointServer = (DefaultPinpointServer) channel.getAttachment();
if (pinpointServer != null && pinpointServer.isEnableDuplexCommunication()) {
pinpointServerList.add(pinpointServer);
}
}
return pinpointServerList;
}
class PinpointServerChannelHandler extends SimpleChannelHandler {
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
final Channel channel = e.getChannel();
logger.info("channelConnected channel:{}", channel);
if (released) {
logger.warn("already released. channel:{}", channel);
channel.write(new ServerClosePacket()).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
future.getChannel().close();
}
});
return;
}
boolean isIgnore = isIgnoreAddress(channel);
if (isIgnore) {
logger.debug("channelConnected ignore address. channel:" + channel);
return;
}
DefaultPinpointServer pinpointServer = createPinpointServer(channel);
channel.setAttachment(pinpointServer);
channelGroup.add(channel);
pinpointServer.start();
super.channelConnected(ctx, e);
}
@Override
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
final Channel channel = e.getChannel();
DefaultPinpointServer pinpointServer = (DefaultPinpointServer) channel.getAttachment();
if (pinpointServer != null) {
pinpointServer.stop(released);
}
super.channelDisconnected(ctx, e);
}
// ChannelClose event may also happen when the other party close socket
// first and Disconnected occurs
// Should consider that.
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
final Channel channel = e.getChannel();
channelGroup.remove(channel);
super.channelClosed(ctx, e);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
final Channel channel = e.getChannel();
DefaultPinpointServer pinpointServer = (DefaultPinpointServer) channel.getAttachment();
if (pinpointServer != null) {
Object message = e.getMessage();
pinpointServer.messageReceived(message);
}
super.messageReceived(ctx, e);
}
}
}