package lsr.paxos.replica; import static lsr.common.ProcessDescriptor.processDescriptor; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import lsr.common.ProcessDescriptor; import lsr.common.nio.AcceptHandler; import lsr.common.nio.ReaderAndWriter; import lsr.common.nio.SelectorThread; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is responsible for accepting new connections from the client. It * uses java nio package, so it is possible to handle more client connections. * Every client connection is then handled by new <code>NioClientProxy</code> * instance. To start waiting for client connection, start method has to be * invoked. * * @see NioClientProxy */ public class NioClientManager implements AcceptHandler { private final SelectorThread[] selectorThreads; private int nextThread = 0; private final ClientRequestManager requestManager; private ServerSocketChannel serverSocketChannel = null; /** * Creates new client manager. * * @param localPort - the listen port for client connections * @param requestManager - callback invoked every time new message is * received by client * @param idGenerator - generator used to allocate id's for clients * @throws IOException if creating selector failed */ public NioClientManager(ClientRequestManager requestManager) throws IOException { this.requestManager = requestManager; requestManager.setClientManager(this); int nSelectors = processDescriptor.selectorThreadCount; if (nSelectors == -1) { nSelectors = NioClientManager.computeNSelectors(); } else { if (nSelectors < 0) { throw new IOException("Invalid value for property " + ProcessDescriptor.SELECTOR_THREADS + ": " + nSelectors); } } logger.info("Real {}={}", ProcessDescriptor.SELECTOR_THREADS, nSelectors); selectorThreads = new SelectorThread[nSelectors]; for (int i = 0; i < selectorThreads.length; i++) { selectorThreads[i] = new SelectorThread(i); } } public void executeInAllSelectors(Runnable r) { for (SelectorThread sThread : selectorThreads) { sThread.beginInvoke(r); } } /** * Starts listening and handling client connections. * * @throws IOException if error occurs while preparing socket channel */ public void start() throws IOException { assert serverSocketChannel == null : "Start called more than once"; serverSocketChannel = ServerSocketChannel.open(); int localPort = processDescriptor.getLocalProcess().getClientPort(); serverSocketChannel.socket().bind(new InetSocketAddress(localPort)); SelectorThread selectorThread = getNextThread(); selectorThread.scheduleRegisterChannel(serverSocketChannel, SelectionKey.OP_ACCEPT, this); for (int i = 0; i < selectorThreads.length; i++) { selectorThreads[i].start(); } } /** * This method is called by <code>SelectorThread</code> every time new * connection from client can be accepted. After accepting, new * <code>NioClientProxy</code> is created to handle this connection. */ public void handleAccept() { SocketChannel socketChannel = null; try { socketChannel = serverSocketChannel.accept(); } catch (IOException e) { /* * TODO: (NS) probably too many open files exception, but i don't * know what to do then; is server socket channel valid after * throwing this exception?; if yes can we just ignore it and wait * for new connections? */ throw new RuntimeException(e); } selectorThreads[0].addChannelInterest(serverSocketChannel, SelectionKey.OP_ACCEPT); assert socketChannel != null; try { SelectorThread selectorThread = getNextThread(); ReaderAndWriter raw = new ReaderAndWriter(socketChannel, selectorThread); new NioClientProxy(raw, requestManager); if (logger.isDebugEnabled()) { logger.debug("Connection from {}", socketChannel.socket().getInetAddress()); } } catch (IOException e) { throw new RuntimeException("Failed to set tcpNoDelay somewhere below. Let's die.", e); } } public SelectorThread getNextThread() { SelectorThread t = selectorThreads[nextThread]; nextThread = (nextThread + 1) % selectorThreads.length; return t; } private static int computeNSelectors() { int nProcessors = Runtime.getRuntime().availableProcessors(); int n; /* * (NS) Values determined empirically based on tests on 24 core Opteron * system. */ if (nProcessors < 3) { n = 1; } else if (nProcessors < 5) { n = 2; } else if (nProcessors < 7) { n = 3; } else if (nProcessors < 9) { n = 4; } else if (nProcessors < 17) { n = 5; } else { n = 6; } logger.info( "Number of selector threads selected basing on static tables. Processors: {}, selectors: ", nProcessors, n); return n; } private final static Logger logger = LoggerFactory.getLogger(NioClientManager.class); }