org.ow2.petals.microkernel.transport.platform.nio.server.NioServerAgent Maven / Gradle / Ivy
/**
* Copyright (c) 2009-2012 Capgemini, 2009-2012 EBM WebSourcing, 2012-2016 Linagora
*
* This program/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, either version 2.1 of the License, or (at your
* option) any later version.
*
* This program/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 program/library; If not, see http://www.gnu.org/licenses/
* for the GNU Lesser General Public License version 2.1.
*/
package org.ow2.petals.microkernel.transport.platform.nio.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
import org.objectweb.fractal.fraclet.annotations.Component;
import org.objectweb.fractal.fraclet.annotations.Interface;
import org.objectweb.fractal.fraclet.annotations.Lifecycle;
import org.objectweb.fractal.fraclet.annotations.Requires;
import org.objectweb.fractal.fraclet.types.Step;
import org.ow2.petals.basisapi.exception.PetalsException;
import org.ow2.petals.microkernel.api.configuration.ConfigurationService;
import org.ow2.petals.microkernel.api.transport.FastRemoteTransportService;
import org.ow2.petals.microkernel.transport.Transporter;
import org.ow2.petals.microkernel.transport.platform.nio.monitoring.TcpTransporterMonitoring;
import org.ow2.petals.microkernel.transport.platform.nio.selector.InputSocketChannelContext;
import org.ow2.petals.microkernel.transport.platform.nio.selector.NioSelector;
import org.ow2.petals.microkernel.transport.platform.nio.selector.NioServer;
import org.ow2.petals.microkernel.transport.platform.nio.selector.PipedDeserializer;
import com.ebmwebsourcing.easycommons.log.LoggingUtil;
/**
* The server agent of the NIO transporter.
*
* @author Christophe DENEUX - Capgemini Sud
*/
@Component(provides = @Interface(name = "service", signature = NioServer.class) )
public class NioServerAgent implements NioServer {
/**
* The socket selector Fractal component to use to process network IO.
*/
@Requires(name = NioSelector.NIO_SELECTOR_ITF)
protected NioSelector selectorAgent;
@Requires(name = NioReceiver.NIO_MSG_RECEIVER_ITF)
protected NioReceiver messageReceiver;
/**
* Interface used to access configuration
*/
@Requires(name = "configuration")
protected ConfigurationService configurationService;
/**
* The TCP transporter monitoring service - Fractal component
*/
@Requires(name = "transportermonitoring")
private TcpTransporterMonitoring tcpTransporterMonitoring;
private final LoggingUtil log = new LoggingUtil(Logger.getLogger(Constants.FRACTAL_COMPONENT_LOGGER_NAME));
/**
* The server socket on which connections are waited.
*/
private InetSocketAddress serverSocket;
/**
* The server socket channel on which connections are waited.
*/
private ServerSocketChannel serverSocketChannel;
/**
* Map containing the deserialization task associated to each socket channel.
*/
private final Map deserializationTasks = new ConcurrentHashMap();
/**
* Acceptor thread pool. An acceptor is in charge to deserialize received data into its associated object and
* transmit it to the {@link Transporter}.
*/
private ThreadPoolExecutor threadPoolTaskExecutor = null;
/**
*
* Counters about current connections established, with their state ACTIVE (index {@link #CONNECTION_STATE_ACTIVE})
* or ESTABLISHED (index {@link #CONNECTION_STATE_ESTABLISHED}), per remote container identified by its IP address.
*
*
* The number of IDLE connections is the number of ESTABLISHED connections minus the number of ACTIVE connections.
*
*/
private final ConcurrentMap currentConnections = new ConcurrentHashMap();
/**
* Index into atomic long array of {@link #currentConnections} for ACTIVE connections
*/
private final static int CONNECTION_STATE_ACTIVE = 0;
/**
* Index into atomic long array of {@link #currentConnections} for an established connections (active or idle)
*/
private final static int CONNECTION_STATE_ESTABLISHED = 1;
@Lifecycle(step = Step.START)
public void start() throws Exception {
this.log.start();
this.currentConnections.clear();
final int listenPort = this.configurationService.getContainerConfiguration().getTCPPort();
String host = this.configurationService.getContainerConfiguration().getTCPListen();
if (host == null || host.isEmpty()) {
host = FastRemoteTransportService.DEFAULT_RECEIVER_LISTENING_ITF;
this.log.config(
"No value or empty value for the TCP listen interface, all network interfaces of the container host are used.");
}
this.serverSocket = new InetSocketAddress(host, listenPort);
this.log.debug("Starting NIO transporter server on " + this.serverSocket + "...");
this.serverSocketChannel = ServerSocketChannel.open();
// Caution, as the queue of the thread pool is a LinkedBlockingQueue,
// the parameters maxPoolSize has no sense. The keep alive time is used to decrease the core size when no
// request incomes.
final int nbAcceptors = this.configurationService.getContainerConfiguration().getTCPReceivers();
final long keepAliveTime = this.configurationService.getContainerConfiguration().getTCPReceiversKeepAlive();
this.threadPoolTaskExecutor = new ThreadPoolExecutor(nbAcceptors, nbAcceptors, keepAliveTime,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new DeserializerThreadFactory(this.log));
this.threadPoolTaskExecutor.allowCoreThreadTimeOut(true);
// TODO: Set socket options
// this.serverSocketChannel.socket().setPerformancePreferences(0, 1, 2);
try {
this.serverSocketChannel.socket().bind(this.serverSocket, 100);
} catch (final IOException e) {
throw new PetalsException("The NIO tranporter can't listen on " + this.serverSocket, e);
}
this.serverSocketChannel.configureBlocking(false);
this.log.info("The NIO transporter server is ready to process requests on " + this.serverSocket + ".");
this.selectorAgent.register(SelectionKey.OP_ACCEPT, this.serverSocketChannel, this);
this.log.end();
}
@Lifecycle(step = Step.STOP)
public void stop() {
this.log.start();
this.log.debug("Stopping NIO Server agent.");
try {
this.serverSocketChannel.close();
} catch (final Exception e) {
this.log.warning("Can't close the channel", e);
}
this.log.info("The NIO Server agent is stopped.");
this.threadPoolTaskExecutor.shutdownNow();
this.log.end();
}
public void onConnectionIsAccepted(final SocketChannel socketChannel, final Selector selector) throws IOException {
final Socket socket = socketChannel.socket();
this.log.info("A connection is accepted: " + socket.toString());
final String containerIpAddress = socket.getInetAddress().getHostAddress();
this.tcpTransporterMonitoring.newIncomingConnection(containerIpAddress);
final AtomicLong[] currentConnectionForContainer = this.currentConnections.putIfAbsent(containerIpAddress,
new AtomicLong[] { new AtomicLong(0), new AtomicLong(1) });
if (currentConnectionForContainer != null) {
currentConnectionForContainer[CONNECTION_STATE_ESTABLISHED].incrementAndGet();
}
tcpTransporterMonitoring.pickIncomingConnectionNumbers(containerIpAddress);
final PipedDeserializer deserializer = new PipedDeserializerImpl(socket, this.messageReceiver,
this.tcpTransporterMonitoring, this.log.getLogger(), this.log);
this.deserializationTasks.put(socketChannel, deserializer);
socketChannel.register(selector, SelectionKey.OP_READ, new InputSocketChannelContext(socketChannel,
deserializer, this));
}
public void onRead(final PipedDeserializer deserializer) {
try {
this.threadPoolTaskExecutor.execute(deserializer);
} catch (final RejectedExecutionException e) {
this.log.error("A message deserialization task has been rejected (" + e.getMessage() + ")");
}
}
public void onClose(final SocketChannel socketChannel) {
// We free the deserializer associated to the socket channel
this.deserializationTasks.remove(socketChannel);
final Socket socket = socketChannel.socket();
final String containerIpAddress = socket.getInetAddress().getHostAddress();
final AtomicLong[] currentConnectionForContainer = this.currentConnections.putIfAbsent(containerIpAddress,
new AtomicLong[] { new AtomicLong(0), new AtomicLong(0) });
if (currentConnectionForContainer != null) {
currentConnectionForContainer[CONNECTION_STATE_ESTABLISHED].decrementAndGet();
}
tcpTransporterMonitoring.pickIncomingConnectionNumbers(containerIpAddress);
this.log.info("A connection is released: " + socket.toString());
}
@Override
public void onConnectionActive(final SocketChannel socketChannel) {
final String containerIpAddress = socketChannel.socket().getInetAddress().getHostAddress();
final AtomicLong[] currentConnectionForContainer = this.currentConnections.putIfAbsent(containerIpAddress,
new AtomicLong[] { new AtomicLong(1), new AtomicLong(0) });
if (currentConnectionForContainer != null) {
currentConnectionForContainer[CONNECTION_STATE_ACTIVE].incrementAndGet();
}
this.tcpTransporterMonitoring.pickIncomingConnectionNumbers(containerIpAddress);
this.tcpTransporterMonitoring.incPendingIncomingMessagesProbe(containerIpAddress);
}
@Override
public void onConnectionIdle(final SocketChannel socketChannel) {
final String containerIpAddress = socketChannel.socket().getInetAddress().getHostAddress();
final AtomicLong[] currentConnectionForContainer = this.currentConnections.putIfAbsent(containerIpAddress,
new AtomicLong[] { new AtomicLong(0), new AtomicLong(1) });
if (currentConnectionForContainer != null) {
currentConnectionForContainer[CONNECTION_STATE_ACTIVE].decrementAndGet();
}
this.tcpTransporterMonitoring.pickIncomingConnectionNumbers(containerIpAddress);
// Here the data was read from the media, but now not converted into a message exchange, so the receipt is
// always in progress. See PipedDeserializeImpl.run().
}
@Override
public long getNumActiveConnections(final String containerIpAddress) {
return this.currentConnections.get(containerIpAddress)[CONNECTION_STATE_ACTIVE].longValue();
}
@Override
public long getNumIdleConnections(final String containerIpAddress) {
final AtomicLong[] values = this.currentConnections.get(containerIpAddress);
return values[CONNECTION_STATE_ESTABLISHED].longValue() - values[CONNECTION_STATE_ACTIVE].longValue();
}
}