/** * Copyright 2008 - CommonCrawl Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * **/ package org.commoncrawl.io; import java.io.IOException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.util.StringUtils; import org.commoncrawl.async.EventLoop; import org.commoncrawl.util.CCStringUtils; /** * NIOSocketSelector - class used to poll a set of asynchronous NIOSocket * sockets. * * @author rana * */ public class NIOSocketSelector { private static class PendingRegistration { NIOSocket _socket; int _interestOps; PendingRegistration(NIOSocket socket, int socketOp) { _socket = socket; _interestOps = socketOp; } public int getInterestOps() { return _interestOps; } public NIOSocket getSocket() { return _socket; } public void setInterestOps(int ops) { _interestOps = ops; } } public static class TimeUsageDetail { public long blockedTime; public long unblockedTime; public long timeInConnectedEvt; public long timeInReadableEvt; public long timeInWritableEvt; void reset() { blockedTime = 0; unblockedTime = 0; timeInConnectedEvt = 0; timeInReadableEvt = 0; timeInWritableEvt = 0; } } Selector _selector = null; EventLoop _eventLoop = null; public static final Log LOG = LogFactory.getLog("org.commoncrawl.io.NIOSocketSelector"); Map<Integer, PendingRegistration> _pendingRegistrations = new TreeMap<Integer, PendingRegistration>(); private long _lastPollTime = -1; /** Constructor - creates a new NIO Selector */ public NIOSocketSelector(EventLoop eventLoop) throws IOException { _eventLoop = eventLoop; _selector = Selector.open(); } /** cancel all event registrations on the specified object (socket) */ public void cancelRegistration(NIOSocket theSocket) { if (_eventLoop == null || _eventLoop.getEventThread() == Thread.currentThread()) { if (theSocket.getChannel() != null) { SelectionKey key = theSocket.getChannel().keyFor(_selector); if (key != null) { key.cancel(); } } } else { synchronized (_pendingRegistrations) { _pendingRegistrations.put(theSocket.getSocketId(), new PendingRegistration(theSocket, 0)); } _eventLoop.wakeup(); } } /** * poll method - poll the registered socket for events and potentially block * for IO for the specified timeout value * * @param timeoutValue * - amount of time in MS to wait (block) for IO * * */ public int poll(long timeoutValue, TimeUsageDetail timeUsageDetailOut) throws IOException { long timeStart = System.currentTimeMillis(); if (_lastPollTime != -1 && (timeStart - _lastPollTime) >= 30000) { LOG.error("POLL Delta Too Long:" + (timeStart - _lastPollTime)); } _lastPollTime = timeStart; if (timeUsageDetailOut != null) { timeUsageDetailOut.blockedTime = 0; timeUsageDetailOut.unblockedTime = 0; } if (_selector == null || !_selector.isOpen()) { IOException e = new IOException("Selector NULL or Selector is Not Open!"); LOG.error(e); throw e; } processPendingRegistrations(); long timeEnd = System.currentTimeMillis(); if (timeUsageDetailOut != null) { timeUsageDetailOut.unblockedTime += (timeEnd - timeStart); } timeStart = System.currentTimeMillis(); int count = _selector.select(timeoutValue); timeEnd = System.currentTimeMillis(); if (timeUsageDetailOut != null) { timeUsageDetailOut.blockedTime += (timeEnd - timeStart); } long unblockedTimeStart = System.currentTimeMillis(); // if (count != 0 ) { Set<SelectionKey> selectionSet = _selector.selectedKeys(); for (Iterator<SelectionKey> i = selectionSet.iterator(); i.hasNext();) { SelectionKey selectionKey = i.next(); i.remove(); if (selectionKey.isValid()) { NIOSocket theSocket = (NIOSocket) selectionKey.attachment(); if (theSocket != null && theSocket.getListener() != null) { // reset interest ops selectionKey.interestOps(0); // process events in key ... if (selectionKey.isConnectable()) { boolean connected = false; Exception disconnectException = null; try { if (((NIOClientSocket) theSocket).finishConnect()) { connected = true; // log it ... // LOG.info("Connected to:"+((NIOClientSocket)theSocket).getSocketAddress()); // reset the selection key's ops.. otherwise, select keeps // returning on an already connected socket (since we have // registered for CONNECT) // System.out.println("Connected to:" + ((NIOClientSocket) theSocket).getSocketAddress()); timeStart = System.currentTimeMillis(); ((NIOClientSocketListener) theSocket.getListener()).Connected((NIOClientSocket) theSocket); if (timeUsageDetailOut != null) { timeUsageDetailOut.timeInConnectedEvt += System.currentTimeMillis() - timeStart; } } else { // LOG.error("Failed to Connect to:"+((NIOClientSocket)theSocket).getSocketAddress() // + " - finishConnect returned false"); theSocket.close(); } } catch (IOException e) { // LOG.error("Failed to Connect to:"+((NIOClientSocket)theSocket).getSocketAddress() // + " with Exception:"+e); theSocket.close(); disconnectException = e; } catch (Exception e) { LOG.fatal("Caught Runtime Exception in Connected Event:" + CCStringUtils.stringifyException(e)); ((NIOClientSocketListener) theSocket.getListener()).Excepted(theSocket, e); theSocket.close(); disconnectException = e; } // if we were unable to properly establish the connection, trigger // the Disconnected notification ... if (!connected) { // LOG.error("Failed to Complete Connection in Finish Connect- Calling Disconnected"); ((NIOClientSocketListener) theSocket.getListener()).Disconnected(theSocket, disconnectException); // continue to the next socket ... continue; } } // now always set the socket to readable state ... if ((theSocket instanceof NIOClientSocket) && selectionKey.isValid()) { selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_READ); } if (selectionKey.isValid() && selectionKey.isReadable()) { int bytesRead = -1; try { timeStart = System.currentTimeMillis(); // track the number of actual bytes read in the callback ... bytesRead = ((NIOClientSocketListener) theSocket.getListener()).Readable((NIOClientSocket) theSocket); // System.out.println("Readable Took:" + // (System.currentTimeMillis() - timeStart)); if (timeUsageDetailOut != null) { timeUsageDetailOut.timeInReadableEvt += System.currentTimeMillis() - timeStart; } if (bytesRead == -1) { // log it ... // LOG.error("Abnormal Disconnect Detected on Socket:"+ // ((NIOClientSocket)theSocket).getSocketAddress()); // trigger a disconnect event ... ((NIOClientSocketListener) theSocket.getListener()).Disconnected(theSocket, null); // close the socket ... theSocket.close(); } } catch (RuntimeException e) { LOG.error("Caught Runtime Exception in Readable Event:" + StringUtils.stringifyException(e)); ((NIOClientSocketListener) theSocket.getListener()).Excepted(theSocket, e); theSocket.close(); // KILL THE SERVER throw e; } // if bytesRead == -1 then this means that the underlying connection // has gone bad ... } if (selectionKey.isValid() && selectionKey.isWritable()) { try { timeStart = System.currentTimeMillis(); ((NIOClientSocketListener) theSocket.getListener()).Writeable((NIOClientSocket) theSocket); // System.out.println("Writable Took:" + // (System.currentTimeMillis() - timeStart)); if (timeUsageDetailOut != null) { timeUsageDetailOut.timeInWritableEvt += System.currentTimeMillis() - timeStart; } } catch (RuntimeException e) { LOG.error("Caught Runtime Exception in Readable Event:" + StringUtils.stringifyException(e)); ((NIOClientSocketListener) theSocket.getListener()).Excepted(theSocket, e); theSocket.close(); // KILL THE SERVER throw e; } } if (selectionKey.isValid() && selectionKey.isAcceptable()) { ((NIOServerSocket) theSocket).acceptable(); // re-register for accept on this socket selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_ACCEPT); } } } else { LOG.error("Invalid Socket Detected. Calling Disconnect"); NIOSocket theSocket = (NIOSocket) selectionKey.attachment(); if (theSocket != null && theSocket.getListener() != null) { theSocket.getListener().Disconnected(theSocket, null); } } } long unblockedTimeEnd = System.currentTimeMillis(); if (timeUsageDetailOut != null) { timeUsageDetailOut.unblockedTime += (unblockedTimeEnd - unblockedTimeStart); } // } return count; } private final void processPendingRegistrations() { synchronized (_pendingRegistrations) { if (_pendingRegistrations.size() != 0) { for (PendingRegistration registration : _pendingRegistrations.values()) { if (registration.getInterestOps() == 0) { cancelRegistration(registration.getSocket()); } else { try { registerSocket(registration.getSocket(), registration.getInterestOps()); } catch (IOException e) { LOG.error("registerSocket threw Exception:" + e.getMessage()); } } } _pendingRegistrations.clear(); } } } /** register a socket for ACCEPT events */ public void registerForAccept(NIOServerSocket theSocket) throws IOException { registerSocket(theSocket, SelectionKey.OP_ACCEPT); } /** register a socket for CONNECT events */ public void registerForConnect(NIOClientSocket theSocket) throws IOException { registerSocket(theSocket, SelectionKey.OP_CONNECT); } /** register a socket for READ events */ public void registerForRead(NIOClientSocket theSocket) throws IOException { registerSocket(theSocket, SelectionKey.OP_READ); } /** register a socket for READ AND WRITE events */ public void registerForReadAndWrite(NIOClientSocket theSocket) throws IOException { registerSocket(theSocket, SelectionKey.OP_READ | SelectionKey.OP_WRITE); } /** register a socket for WRITE events */ public void registerForWrite(NIOClientSocket theSocket) throws IOException { registerSocket(theSocket, SelectionKey.OP_WRITE); } /** internal registration helper */ private void registerSocket(NIOSocket theSocket, int interestOps) throws IOException { if (_eventLoop == null || _eventLoop.getEventThread() == Thread.currentThread()) { SelectionKey key = theSocket.getChannel().keyFor(_selector); if (key == null) { if (theSocket.readsDisabled()) { interestOps = (interestOps & ~SelectionKey.OP_READ); } if (interestOps != 0) { key = theSocket.getChannel().register(_selector,interestOps,theSocket); } } else { key.interestOps(key.interestOps() | interestOps); if (theSocket.readsDisabled()) { key.interestOps(key.interestOps() & ~SelectionKey.OP_READ); } } } else { synchronized(_pendingRegistrations) { PendingRegistration pendingRegistration = _pendingRegistrations.get(theSocket.getSocketId()); if (pendingRegistration == null) { _pendingRegistrations.put(theSocket.getSocketId(),new PendingRegistration(theSocket,interestOps)); } else { pendingRegistration.setInterestOps(pendingRegistration.getInterestOps() | interestOps); } } _eventLoop.wakeup(); } } /** * wakeup the potentially blocked primary poll thread - used by a worker * thread to unblock the primary poll thread when a non-io event has resulted * in a condition that needs to be addressed in the primary thread context. * * @throws IOException */ public void wakeup() throws IOException { if (_selector == null || !_selector.isOpen()) { IOException e = new IOException("Selector NULL or Selector is Not Open!"); LOG.error(e); throw e; } _selector.wakeup(); } }