/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.facebook.infrastructure.net; import java.io.IOException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.TreeSet; import org.apache.log4j.Logger; import com.facebook.infrastructure.utils.*; /** * Author : Avinash Lakshman ( alakshman@facebook.com) & Prashant Malik ( pmalik@facebook.com ) */ public class SelectorManager extends Thread { private static final Logger logger_ = Logger.getLogger(SelectorManager.class); // the underlying selector used /** * DESCRIBE THE FIELD */ protected Selector selector_; protected HashSet<SelectionKey> modifyKeysForRead_; protected HashSet<SelectionKey> modifyKeysForWrite_; // the list of keys waiting to be cancelled protected HashSet<SelectionKey> cancelledKeys_; // The static selector manager which is used by all applications private static SelectorManager manager_; // The static UDP selector manager which is used by all applications private static SelectorManager udpManager_; /** * Constructor, which is private since there is only one selector per JVM. * * @param profile * DESCRIBE THE PARAMETER */ protected SelectorManager(String name) { super(name); this.modifyKeysForRead_ = new HashSet<SelectionKey>(); this.modifyKeysForWrite_ = new HashSet<SelectionKey>(); this.cancelledKeys_ = new HashSet<SelectionKey>(); // attempt to create selector try { selector_ = Selector.open(); } catch (IOException e) { logger_.error("SEVERE ERROR (SelectorManager): Error creating selector " + e); } setDaemon(false); start(); } /** * Method which asks the Selector Manager to add the given key to the * cancelled set. If noone calls register on this key during the rest of * this select() operation, the key will be cancelled. Otherwise, it will be * returned as a result of the register operation. * * @param key * The key to cancel */ public void cancel(SelectionKey key) { if (key == null) { throw new NullPointerException(); } synchronized ( cancelledKeys_ ) { cancelledKeys_.add(key); } } /** * Registers a new channel with the selector, and attaches the given * SelectionKeyHandler as the handler for the newly created key. Operations * which the hanlder is interested in will be called as available. * * @param channel * The channel to regster with the selector * @param handler * The handler to use for the callbacks * @param ops * The initial interest operations * @return The SelectionKey which uniquely identifies this channel * @exception IOException * DESCRIBE THE EXCEPTION */ public SelectionKey register(SelectableChannel channel, SelectionKeyHandler handler, int ops) throws IOException { if ((channel == null) || (handler == null)) { throw new NullPointerException(); } selector_.wakeup(); SelectionKey key = channel.register(selector_, ops, handler); synchronized(cancelledKeys_) { cancelledKeys_.remove(key); } selector_.wakeup(); return key; } public void modifyKeyForRead(SelectionKey key) { if (key == null) { throw new NullPointerException(); } synchronized(modifyKeysForRead_) { modifyKeysForRead_.add(key); } selector_.wakeup(); } public void modifyKeyForWrite(SelectionKey key) { if (key == null) { throw new NullPointerException(); } synchronized( modifyKeysForWrite_ ) { modifyKeysForWrite_.add(key); } selector_.wakeup(); } /** * This method starts the socket manager listening for events. It is * designed to be started when this thread's start() method is invoked. */ public void run() { try { // loop while waiting for activity while (true && !Thread.currentThread().interrupted() ) { try { doProcess(); selector_.select(1000); synchronized( cancelledKeys_ ) { if (cancelledKeys_.size() > 0) { SelectionKey[] keys = cancelledKeys_.toArray( new SelectionKey[0]); for ( SelectionKey key : keys ) { key.cancel(); key.channel().close(); } cancelledKeys_.clear(); } } } catch ( IOException e ) { logger_.warn(LogUtil.throwableToString(e)); } } manager_ = null; } catch (Throwable t) { logger_.error("ERROR (SelectorManager.run): " + t); logger_.error(LogUtil.throwableToString(t)); System.exit(-1); } } protected void doProcess() throws IOException { doInvocationsForRead(); doInvocationsForWrite(); doSelections(); } /** * DESCRIBE THE METHOD * * @exception IOException * DESCRIBE THE EXCEPTION */ protected void doSelections() throws IOException { SelectionKey[] keys = selectedKeys(); for (int i = 0; i < keys.length; i++) { selector_.selectedKeys().remove(keys[i]); synchronized (keys[i]) { SelectionKeyHandler skh = (SelectionKeyHandler) keys[i] .attachment(); if (skh != null) { // accept if (keys[i].isValid() && keys[i].isAcceptable()) { skh.accept(keys[i]); } // connect if (keys[i].isValid() && keys[i].isConnectable()) { skh.connect(keys[i]); } // read if (keys[i].isValid() && keys[i].isReadable()) { skh.read(keys[i]); } // write if (keys[i].isValid() && keys[i].isWritable()) { skh.write(keys[i]); } } else { keys[i].channel().close(); keys[i].cancel(); } } } } private void doInvocationsForRead() { Iterator<SelectionKey> it; synchronized (modifyKeysForRead_) { it = new ArrayList<SelectionKey>(modifyKeysForRead_).iterator(); modifyKeysForRead_.clear(); } while (it.hasNext()) { SelectionKey key = it.next(); if (key.isValid() && (key.attachment() != null)) { ((SelectionKeyHandler) key.attachment()).modifyKeyForRead(key); } } } private void doInvocationsForWrite() { Iterator<SelectionKey> it; synchronized (modifyKeysForWrite_) { it = new ArrayList<SelectionKey>(modifyKeysForWrite_).iterator(); modifyKeysForWrite_.clear(); } while (it.hasNext()) { SelectionKey key = it.next(); if (key.isValid() && (key.attachment() != null)) { ((SelectionKeyHandler) key.attachment()).modifyKeyForWrite(key); } } } /** * Selects all of the currenlty selected keys on the selector and returns * the result as an array of keys. * * @return The array of keys * @exception IOException * DESCRIBE THE EXCEPTION */ protected SelectionKey[] selectedKeys() throws IOException { return (SelectionKey[]) selector_.selectedKeys().toArray( new SelectionKey[0]); } /** * Returns the SelectorManager applications should use. * * @return The SelectorManager which applications should use */ public static SelectorManager getSelectorManager() { synchronized (SelectorManager.class) { if (manager_ == null) { manager_ = new SelectorManager("TCP Selector Manager"); } } return manager_; } public static SelectorManager getUdpSelectorManager() { synchronized (SelectorManager.class) { if (udpManager_ == null) { udpManager_ = new SelectorManager("UDP Selector Manager"); } } return udpManager_; } /** * Returns whether or not this thread of execution is the selector thread * * @return Whether or not this is the selector thread */ public static boolean isSelectorThread() { return (Thread.currentThread() == manager_); } }