bboss.org.jgroups.blocks.ConnectionTableNIO Maven / Gradle / Ivy
The newest version!
// $Id: ConnectionTableNIO.java,v 1.43 2009/05/13 13:06:54 belaban Exp $
package bboss.org.jgroups.blocks;
import java.io.IOException;
import java.net.BindException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import bboss.org.jgroups.Address;
import bboss.org.jgroups.Global;
import bboss.org.jgroups.logging.Log;
import bboss.org.jgroups.stack.IpAddress;
import bboss.org.jgroups.util.ShutdownRejectedExecutionHandler;
/**
* Manages incoming and outgoing TCP connections. For each outgoing message to destination P, if there
* is not yet a connection for P, one will be created. Subsequent outgoing messages will use this
* connection. For incoming messages, one server socket is created at startup. For each new incoming
* client connecting, a new thread from a thread pool is allocated and listens for incoming messages
* until the socket is closed by the peer.
Sockets/threads with no activity will be killed
* after some time.
*
* Incoming messages from any of the sockets can be received by setting the message listener.
*
* @author Bela Ban, Scott Marlow, Alex Fu
*/
public class ConnectionTableNIO extends BasicConnectionTable implements Runnable {
private ServerSocketChannel m_serverSocketChannel;
private Selector m_acceptSelector;
private WriteHandler[] m_writeHandlers;
private int m_nextWriteHandler = 0;
private final Object m_lockNextWriteHandler = new Object();
private ReadHandler[] m_readHandlers;
private int m_nextReadHandler = 0;
private final Object m_lockNextReadHandler = new Object();
// thread pool for processing read requests
private Executor m_requestProcessors;
private volatile boolean serverStopping=false;
private final List m_backGroundThreads = new LinkedList(); // Collection of all created threads
private int m_reader_threads = 3;
private int m_writer_threads = 3;
private int m_processor_threads = 5; // PooledExecutor.createThreads()
private int m_processor_minThreads = 5; // PooledExecutor.setMinimumPoolSize()
private int m_processor_maxThreads = 5; // PooledExecutor.setMaxThreads()
private int m_processor_queueSize=100; // Number of queued requests that can be pending waiting
// for a background thread to run the request.
private long m_processor_keepAliveTime = Long.MAX_VALUE; // PooledExecutor.setKeepAliveTime( milliseconds);
// negative value used to mean to wait forever, instead set to Long.MAX_VALUE to wait forever
/**
* @param srv_port
* @throws Exception
*/
public ConnectionTableNIO(int srv_port) throws Exception {
this.srv_port=srv_port;
start();
}
/**
* @param srv_port
* @param reaper_interval
* @param conn_expire_time
* @throws Exception
*/
public ConnectionTableNIO(int srv_port, long reaper_interval,
long conn_expire_time) throws Exception {
this.srv_port=srv_port;
this.reaper_interval=reaper_interval;
this.conn_expire_time=conn_expire_time;
start();
}
/**
* @param r
* @param bind_addr
* @param external_addr
* @param srv_port
* @param max_port
* @throws Exception
*/
public ConnectionTableNIO(Receiver r, InetAddress bind_addr, InetAddress external_addr, int srv_port, int max_port)
throws Exception
{
setReceiver(r);
this.external_addr=external_addr;
this.bind_addr=bind_addr;
this.srv_port=srv_port;
this.max_port=max_port;
use_reaper=true;
start();
}
public ConnectionTableNIO(Receiver r, InetAddress bind_addr, InetAddress external_addr,
int srv_port, int max_port, boolean doStart)
throws Exception
{
setReceiver(r);
this.external_addr=external_addr;
this.bind_addr=bind_addr;
this.srv_port=srv_port;
this.max_port=max_port;
use_reaper=true;
if(doStart)
start();
}
/**
* @param r
* @param bind_addr
* @param external_addr
* @param srv_port
* @param max_port
* @param reaper_interval
* @param conn_expire_time
* @throws Exception
*/
public ConnectionTableNIO(Receiver r, InetAddress bind_addr, InetAddress external_addr, int srv_port, int max_port,
long reaper_interval, long conn_expire_time
) throws Exception
{
setReceiver(r);
this.bind_addr=bind_addr;
this.external_addr=external_addr;
this.srv_port=srv_port;
this.max_port=max_port;
this.reaper_interval=reaper_interval;
this.conn_expire_time=conn_expire_time;
use_reaper=true;
start();
}
public ConnectionTableNIO(Receiver r, InetAddress bind_addr, InetAddress external_addr,
int srv_port, int max_port,
long reaper_interval, long conn_expire_time, boolean doStart
) throws Exception
{
setReceiver(r);
this.bind_addr=bind_addr;
this.external_addr=external_addr;
this.srv_port=srv_port;
this.max_port=max_port;
this.reaper_interval=reaper_interval;
this.conn_expire_time=conn_expire_time;
use_reaper=true;
if(doStart)
start();
}
public int getReaderThreads() { return m_reader_threads; }
public void setReaderThreads(int m_reader_threads) {
this.m_reader_threads=m_reader_threads;
}
public int getWriterThreads() { return m_writer_threads; }
public void setWriterThreads(int m_writer_threads) {
this.m_writer_threads=m_writer_threads;
}
public int getProcessorThreads() { return m_processor_threads; }
public void setProcessorThreads(int m_processor_threads) {
this.m_processor_threads=m_processor_threads;
}
public int getProcessorMinThreads() { return m_processor_minThreads;}
public void setProcessorMinThreads(int m_processor_minThreads) {
this.m_processor_minThreads=m_processor_minThreads;
}
public int getProcessorMaxThreads() { return m_processor_maxThreads;}
public void setProcessorMaxThreads(int m_processor_maxThreads) {
this.m_processor_maxThreads=m_processor_maxThreads;
}
public int getProcessorQueueSize() { return m_processor_queueSize; }
public void setProcessorQueueSize(int m_processor_queueSize) {
this.m_processor_queueSize=m_processor_queueSize;
}
public long getProcessorKeepAliveTime() { return m_processor_keepAliveTime; }
public void setProcessorKeepAliveTime(long m_processor_keepAliveTime) {
this.m_processor_keepAliveTime=m_processor_keepAliveTime;
}
/**
* Try to obtain correct Connection (or create one if not yet existent)
*/
ConnectionTable.Connection getConnection(Address dest) throws Exception
{
Connection conn;
SocketChannel sock_ch;
synchronized (conns)
{
conn = (Connection) conns.get(dest);
if (conn == null)
{
InetSocketAddress destAddress = new InetSocketAddress(((IpAddress) dest).getIpAddress(),
((IpAddress) dest).getPort());
sock_ch = SocketChannel.open(destAddress);
sock_ch.socket().setTcpNoDelay(tcp_nodelay);
conn = new Connection(sock_ch, dest);
conn.sendLocalAddress(local_addr);
// This outbound connection is ready
sock_ch.configureBlocking(false);
try
{
if (log.isTraceEnabled())
log.trace("About to change new connection send buff size from " + sock_ch.socket().getSendBufferSize() + " bytes");
sock_ch.socket().setSendBufferSize(send_buf_size);
if (log.isTraceEnabled())
log.trace("Changed new connection send buff size to " + sock_ch.socket().getSendBufferSize() + " bytes");
}
catch (IllegalArgumentException ex)
{
if (log.isErrorEnabled()) log.error("exception setting send buffer size to " +
send_buf_size + " bytes: " + ex);
}
try
{
if (log.isTraceEnabled())
log.trace("About to change new connection receive buff size from " + sock_ch.socket().getReceiveBufferSize() + " bytes");
sock_ch.socket().setReceiveBufferSize(recv_buf_size);
if (log.isTraceEnabled())
log.trace("Changed new connection receive buff size to " + sock_ch.socket().getReceiveBufferSize() + " bytes");
}
catch (IllegalArgumentException ex)
{
if (log.isErrorEnabled()) log.error("exception setting receive buffer size to " +
send_buf_size + " bytes: " + ex);
}
int idx;
synchronized (m_lockNextWriteHandler)
{
idx = m_nextWriteHandler = (m_nextWriteHandler + 1) % m_writeHandlers.length;
}
conn.setupWriteHandler(m_writeHandlers[idx]);
// Put the new connection to the queue
try
{
synchronized (m_lockNextReadHandler)
{
idx = m_nextReadHandler = (m_nextReadHandler + 1) % m_readHandlers.length;
}
m_readHandlers[idx].add(conn);
} catch (InterruptedException e)
{
if (log.isWarnEnabled())
log.warn("Thread (" +Thread.currentThread().getName() + ") was interrupted, closing connection", e);
// What can we do? Remove it from table then.
conn.destroy();
throw e;
}
// Add connection to table
addConnection(dest, conn);
notifyConnectionOpened(dest);
if (log.isTraceEnabled()) log.trace("created socket to " + dest);
}
return conn;
}
}
public final void start() throws Exception {
super.start();
init();
srv_sock=createServerSocket(srv_port, max_port);
if (external_addr!=null)
local_addr=new IpAddress(external_addr, srv_sock.getLocalPort());
else if (bind_addr != null)
local_addr=new IpAddress(bind_addr, srv_sock.getLocalPort());
else
local_addr=new IpAddress(srv_sock.getLocalPort());
if(log.isDebugEnabled()) log.debug("server socket created on " + local_addr);
//Roland Kurmann 4/7/2003, put in thread_group
acceptor=getThreadFactory().newThread(thread_group, this, "ConnectionTable.AcceptorThread");
acceptor.setDaemon(true);
acceptor.start();
m_backGroundThreads.add(acceptor);
// start the connection reaper - will periodically remove unused connections
if(use_reaper && reaper == null) {
reaper=new Reaper();
reaper.start();
}
}
protected void init()
throws Exception
{
// use directExector if max thread pool size is less than or equal to zero.
if(getProcessorMaxThreads() <= 0) {
m_requestProcessors = new Executor() {
public void execute(Runnable command) {
command.run();
}
};
}
else
{
// Create worker thread pool for processing incoming buffers
ThreadPoolExecutor requestProcessors = new ThreadPoolExecutor(getProcessorMinThreads(), getProcessorMaxThreads(),
getProcessorKeepAliveTime(), TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(getProcessorQueueSize()));
requestProcessors.setThreadFactory(new ThreadFactory() {
public Thread newThread(Runnable runnable) {
Thread new_thread=new Thread(thread_group, runnable);
new_thread.setDaemon(true);
new_thread.setName("ConnectionTableNIO.Thread");
m_backGroundThreads.add(new_thread);
return new_thread;
}
});
requestProcessors.setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(requestProcessors.getRejectedExecutionHandler()));
m_requestProcessors = requestProcessors;
}
m_writeHandlers = WriteHandler.create(getThreadFactory(),getWriterThreads(), thread_group, m_backGroundThreads, log);
m_readHandlers = ReadHandler.create(getThreadFactory(),getReaderThreads(), this, thread_group, m_backGroundThreads, log);
}
/**
* Closes all open sockets, the server socket and all threads waiting for incoming messages
*/
public void stop()
{
super.stop();
serverStopping = true;
if(reaper != null)
reaper.stop();
// Stop the main selector
if(m_acceptSelector != null)
m_acceptSelector.wakeup();
// Stop selector threads
if(m_readHandlers != null)
{
for (int i = 0; i < m_readHandlers.length; i++)
{
try
{
m_readHandlers[i].add(new Shutdown());
} catch (InterruptedException e)
{
log.error("Thread ("+Thread.currentThread().getName() +") was interrupted, failed to shutdown selector", e);
}
}
}
if(m_writeHandlers != null)
{
for (int i = 0; i < m_writeHandlers.length; i++)
{
try
{
m_writeHandlers[i].queue.put(new Shutdown());
m_writeHandlers[i].selector.wakeup();
} catch (InterruptedException e)
{
log.error("Thread ("+Thread.currentThread().getName() +") was interrupted, failed to shutdown selector", e);
}
}
}
// Stop the callback thread pool
if(m_requestProcessors instanceof ThreadPoolExecutor)
((ThreadPoolExecutor)m_requestProcessors).shutdownNow();
if(m_requestProcessors instanceof ThreadPoolExecutor){
try{
((ThreadPoolExecutor) m_requestProcessors).awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME,
TimeUnit.MILLISECONDS);
}catch(InterruptedException e){
}
}
// then close the connections
synchronized(conns) {
Iterator it=conns.values().iterator();
while(it.hasNext()) {
Connection conn=(Connection)it.next();
conn.destroy();
}
conns.clear();
}
while(!m_backGroundThreads.isEmpty()) {
Thread t =m_backGroundThreads.remove(0);
try {
t.join();
} catch(InterruptedException e) {
log.error("Thread ("+Thread.currentThread().getName() +") was interrupted while waiting on thread " + t.getName() + " to finish.");
}
}
m_backGroundThreads.clear();
}
/**
* Acceptor thread. Continuously accept new connections and assign readhandler/writehandler
* to them.
*/
public void run() {
Connection conn;
while(m_serverSocketChannel.isOpen() && !serverStopping) {
int num;
try {
num=m_acceptSelector.select();
}
catch(IOException e) {
if(log.isWarnEnabled())
log.warn("Select operation on listening socket failed", e);
continue; // Give up this time
}
if(num > 0) {
Set readyKeys=m_acceptSelector.selectedKeys();
for(Iterator i=readyKeys.iterator(); i.hasNext();) {
SelectionKey key=i.next();
i.remove();
// We only deal with new incoming connections
ServerSocketChannel readyChannel=(ServerSocketChannel)key.channel();
SocketChannel client_sock_ch;
try {
client_sock_ch=readyChannel.accept();
}
catch(IOException e) {
if(log.isWarnEnabled())
log.warn("Attempt to accept new connection from listening socket failed", e);
// Give up this connection
continue;
}
if(log.isTraceEnabled())
log.trace("accepted connection, client_sock=" + client_sock_ch.socket());
try {
client_sock_ch.socket().setSendBufferSize(send_buf_size);
}
catch(IllegalArgumentException ex) {
if(log.isErrorEnabled()) log.error("exception setting send buffer size to " + send_buf_size + " bytes: ", ex);
}
catch(SocketException e) {
if(log.isErrorEnabled()) log.error("exception setting send buffer size to " + send_buf_size + " bytes: ", e);
}
try {
client_sock_ch.socket().setReceiveBufferSize(recv_buf_size);
}
catch(IllegalArgumentException ex) {
if(log.isErrorEnabled()) log.error("exception setting receive buffer size to " + send_buf_size + " bytes: ", ex);
}
catch(SocketException e) {
if(log.isErrorEnabled()) log.error("exception setting receive buffer size to " + recv_buf_size + " bytes: ", e);
}
conn=new Connection(client_sock_ch, null);
try {
Address peer_addr=conn.readPeerAddress(client_sock_ch.socket());
conn.peer_addr=peer_addr;
synchronized(conns) {
Connection tmp=(Connection)conns.get(peer_addr);
if(tmp != null) {
if(peer_addr.compareTo(local_addr) > 0) {
if(log.isTraceEnabled())
log.trace("peer's address (" + peer_addr + ") is greater than our local address (" +
local_addr + "), replacing our existing connection");
// peer's address is greater, add peer's connection to ConnectionTable, destroy existing connection
addConnection(peer_addr, conn);
tmp.destroy();
notifyConnectionOpened(peer_addr);
}
else {
if(log.isTraceEnabled())
log.trace("peer's address (" + peer_addr + ") is smaller than our local address (" +
local_addr + "), rejecting peer connection request");
conn.destroy();
continue;
}
}
else {
addConnection(peer_addr, conn);
}
}
notifyConnectionOpened(peer_addr);
client_sock_ch.configureBlocking(false);
}
catch(IOException e) {
if(log.isWarnEnabled())
log.warn("Attempt to configure non-blocking mode failed", e);
conn.destroy();
continue;
}
catch(Exception e) {
if(log.isWarnEnabled())
log.warn("Attempt to handshake with other peer failed", e);
conn.destroy();
continue;
}
int idx;
synchronized(m_lockNextWriteHandler) {
idx=m_nextWriteHandler=(m_nextWriteHandler + 1) % m_writeHandlers.length;
}
conn.setupWriteHandler(m_writeHandlers[idx]);
try {
synchronized(m_lockNextReadHandler) {
idx=m_nextReadHandler=(m_nextReadHandler + 1) % m_readHandlers.length;
}
m_readHandlers[idx].add(conn);
}
catch(InterruptedException e) {
if(log.isWarnEnabled())
log.warn("Attempt to configure read handler for accepted connection failed", e);
// close connection
conn.destroy();
}
} // end of iteration
} // end of selected key > 0
} // end of thread
if(m_serverSocketChannel.isOpen()) {
try {
m_serverSocketChannel.close();
}
catch(Exception e) {
log.error("exception closing server listening socket", e);
}
}
if(log.isTraceEnabled())
log.trace("acceptor thread terminated");
}
/**
* Finds first available port starting at start_port and returns server socket. Sets srv_port
*/
protected ServerSocket createServerSocket(int start_port, int end_port) throws Exception
{
this.m_acceptSelector = Selector.open();
m_serverSocketChannel = ServerSocketChannel.open();
m_serverSocketChannel.configureBlocking(false);
while (true)
{
try
{
SocketAddress sockAddr;
if (bind_addr == null)
{
sockAddr=new InetSocketAddress(start_port);
m_serverSocketChannel.socket().bind(sockAddr);
}
else
{
sockAddr=new InetSocketAddress(bind_addr, start_port);
m_serverSocketChannel.socket().bind(sockAddr, backlog);
}
}
catch (BindException bind_ex)
{
if (start_port == end_port)
throw (BindException) ((new BindException("No available port to bind to (start_port=" + start_port + ")")).initCause(bind_ex));
start_port++;
continue;
}
catch (SocketException bind_ex)
{
if (start_port == end_port)
throw (BindException) ((new BindException("No available port to bind to (start_port=" + start_port + ")")).initCause(bind_ex));
start_port++;
continue;
}
catch (IOException io_ex)
{
if (log.isErrorEnabled()) log.error("Attempt to bind serversocket failed, port="+start_port+", bind addr=" + bind_addr ,io_ex);
throw io_ex;
}
srv_port = start_port;
break;
}
m_serverSocketChannel.register(this.m_acceptSelector, SelectionKey.OP_ACCEPT);
return m_serverSocketChannel.socket();
}
protected void runRequest(Address addr, ByteBuffer buf) throws InterruptedException {
m_requestProcessors.execute(new ExecuteTask(addr, buf));
}
// Represents shutdown
private static class Shutdown {
}
// ReadHandler has selector to deal with read, it runs in seperated thread
private static class ReadHandler implements Runnable {
private final Selector selector= initHandler();
private final LinkedBlockingQueue