All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBus.net.SelectorThread Maven / Gradle / Ivy

There is a newer version: 7.4.0
Show newest version
/*
  (c) 2004, Nuno Santos, [email protected]
  relased under terms of the GNU public license
  http://www.gnu.org/licenses/licenses.html#TOCGPL

  RCS ID
  $Id: SelectorThread.java,v 1.10 2007/02/07 23:14:01 charlesr Exp $

  CHANGE LOG
  (See the bottom of this file.)
*/

package net.sf.eBus.net;

import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.eBus.config.ENetConfigure.SelectorInfo;

/**
 * Event queue for I/O events raised by a selector. This class
 * receives the lower level events raised by a Selector and
 * dispatches them to the appropriate handler. It also manages
 * all other operations on the selector, like registering and
 * de-registering channels, and updating the events of interest
 * for each monitored socket.
 * 

* This class is inspired by the java.awt.EventQueue and follows * a similar model. The EventQueue class is responsible for * making sure that all operations on AWT objects are performed * on a single thread, the one managed internally by EventQueue. * The SelectorThread class performs a similar task. * In particular: *

    *
  • * Only the thread created by instances of this class should * be allowed to access the selector and all sockets managed * by it. This means that all I/O operations on the sockets * should be performed on the corresponding selector's thread. If some other thread wants to access objects managed by * this selector, then it should use {@link #invokeLater} or * the {@link #invokeAndWait} to dispatch a runnable to this * thread. *
  • *
  • * This thread should not be used to perform lengthy * operations. In particular, it should never be used to * perform blocking I/O operations. To perform a time * consuming task use a worker thread. *
  • *
* This architecture is required for two main reasons: *
    *
  1. * The first, is to make synchronization in the objects of a * connection unnecessary. This is good for performance and * essential for keeping the complexity low. Getting * synchronization right within the objects of a connection * would be extremely tricky. *
  2. *
  3. * The second is to make sure that all actions over the * selector, its keys and related sockets are carried in the * same thread. My personal experience with selectors is that * they don't work well when being accessed concurrently by * several threads. This is mostly the result of bugs in some * of the version of Sun's Java SDK (these problems were * found with version 1.4.2_02). Some of the bugs have * already been identified and fixed by Sun. But it is better * to work around them by avoiding multi-threaded access to * the selector. *
  4. *
* * @author Nuno Santos * @author Charles Rapp */ /* package */ final class SelectorThread extends Thread { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * Initial pending task list size. */ private static final int INITIAL_TASK_SIZE = 32; /** * Bit mask contain all selection key operations. */ private static final int ALL_OPS = (SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE); /** * The selection key operations. */ private static final int[] OPS = { SelectionKey.OP_ACCEPT, SelectionKey.OP_CONNECT, SelectionKey.OP_READ, SelectionKey.OP_WRITE }; /** * Operation keys as human-readable text. */ private static final String[] OP_NAMES = {"accept", "connect", "read", "write"}; //----------------------------------------------------------- // Statics. // /** * Logging subsystem interface. */ private static final Logger sLogger = Logger.getLogger(SelectorThread.class.getName()); //----------------------------------------------------------- // Locals. // /** * Selector used for I/O multiplexing. */ private final ESelector mSelector; /** * The selector is used by this thread. */ private Thread mSelectorThread; /** * Flag telling if this object should terminate, that is, * if it should close the selector and kill the associated * thread. Used for graceful termination. */ private boolean mContinueFlag; /** * List of tasks to be executed in the selector thread. * Submitted using invokeLater() and executed in the main * select loop. */ private final List mPendingInvocations; /** * Set to {@code true} when {@link #mPendingInvocations} is * not empty. This flag is used so that the * {@link #mPendingLock} does not have to be acquired so as * to verify if there are pending invocations. */ private volatile boolean mPendingFlag; /** * Lock protecting access to {@link #mPendingInvocations}. */ private final Lock mPendingLock; /** * This condition is set when {@link #mPendingInvocations} * contains tasks to run. */ private final Condition mNotEmpty; /** * This signal is normally null unless the selector thread * is halting. Method {@link #requestClose()} blocks on this * signal until the select thread halts. */ private CountDownLatch mStopSignal; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new selector thread using the given selector. * @param info selector configuration information. * @throws IOException * if there an I/O error opening the * {@link java.nio.channels.Selector} */ /* package */ SelectorThread(final SelectorInfo info) throws IOException { super (info.name()); mSelector = ESelector.newSelector(info); mContinueFlag = false; mSelectorThread = null; mPendingInvocations = new ArrayList<>(INITIAL_TASK_SIZE); mPendingFlag = false; mPendingLock = new ReentrantLock(); mNotEmpty = mPendingLock.newCondition(); // Make sure this is a daemon thread. this.setDaemon(true); // Make sure this thread runs at maximum priority. this.setPriority(info.priority()); } // end of SelectorThread(SelectorInfo) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Get methods. // /** * Returns true if {@code thread} is the select thread. * @param thread check if this is the select thread. * @return true if {@code thread} is the select thread. */ /* package */ boolean isSelectorThread(final Thread thread) { return (thread == mSelectorThread); } // end of isSelectThread(Thread) /** * Returns {@code true} if this selector thread is running. * @return {@code true} if this selector thread is running. */ /* package */ boolean isRunning() { return (mContinueFlag); } // end of isRunning() // // end of Get methods. //----------------------------------------------------------- /** * Main cycle. This is where event processing and * dispatching happens. */ @Override public void run() { int selectedKeys; Set keys; Iterator it; SelectionKey key; int readyOps; AsyncChannel achannel; mContinueFlag = true; mSelectorThread = Thread.currentThread(); // Here's where everything happens. The select method // will return when any operations registered above have // occurred, the thread has been interrupted, etc. while (mContinueFlag) { // Execute all the pending tasks. doInvocations(); selectedKeys = 0; // Do not enter select if we are halted or there are // no selection keys in place. if (mContinueFlag && !mSelector.keys().isEmpty()) { try { selectedKeys = mSelector.select(); } catch (CancelledKeyException keyex) { // Ignore. } catch (IOException ioex) { // Select should never throw an exception // under normal operation. If this happens, // log the error and try to continue // working. sLogger.log(Level.WARNING, "select failed", ioex); } } if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest( String.format( "SelectorThread: %,d keys selected.", selectedKeys)); } if (selectedKeys > 0) { keys = mSelector.selectedKeys(); // Walk through the collection of ready keys // and dispatch any active event. // Must use an iterator rather than the for-each // loop because the key is removed from the set. for (it = keys.iterator(); it.hasNext();) { key = it.next(); it.remove(); try { // Obtain the interest of the key readyOps = key.readyOps(); achannel = (AsyncChannel) key.attachment(); if (sLogger.isLoggable( Level.FINEST)) { sLogger.finest( String.format( "SelectorThread: processing ops %s.", outputOps(readyOps))); } // Some of the operations set in the // selection key might no longer be // valid afer the handler's // execution. So handlers should take // precautions against this // possibility. achannel.processOps(readyOps, key); } // It is possible for a selection key to be // cancelled before we get a chance to // process its operations. Ignore this // exception and go on to the next key. catch (CancelledKeyException keyex) { // Ignore. } catch (Throwable t) { // No exceptions should be thrown in // the previous block! // So kill everything if one is // detected. Makes debugging easier. mContinueFlag = false; sLogger.log(Level.SEVERE, "select processing error", t); } } } } closeSelectorAndChannels(); // Now let requestClose() know we are finished. if (mStopSignal != null) { mStopSignal.countDown(); } return; } // end of run() /** * Raises an internal flag that will result on this thread * dying the next time it goes through the dispatch loop. * The thread executes all pending tasks before dying. */ /* package */ void requestClose() { mStopSignal = new CountDownLatch(1); mContinueFlag = false; // Nudges the selector. mSelector.wakeup(); // Wait for the selector thread to halt. try { mStopSignal.await(); } catch (InterruptedException interrupt) { // Ignore. } mStopSignal = null; } // end of requestClose() /** * Registers an {@code AsyncChannel} and its operations * with the selector thread. If called within the selector * thread, registration is performed immediately. Otherwise, * it is performed asynchronously on the selector thread. * @param achannel The channel to be monitored. * @param interestOps The interest set. Should be a * combination of SelectionKey constants. * @throws IOException * if there is an I/O error registering the operations with * the channel. */ /* package */ void register(final AsyncChannel achannel, final int interestOps) throws IOException { Objects.requireNonNull(achannel, "achannel is null"); if (Thread.currentThread() == mSelectorThread) { // We are in the selector's thread. No need to // schedule execution. doRegister(achannel, interestOps); } else { invokeLater(() -> { try { doRegister(achannel, interestOps); } catch (IOException ioex) { achannel.handleError(ioex); } }); } return; } // end of register(AsyncChannel, int) /** * Adds a new interest to the list of events where a channel * is registered. This means that the associated event * handler will start receiving events for the specified * interest. * @param achannel The channel to be updated. Must be * registered. * @param interestOps The interest to add. Should be one of * the constants defined on SelectionKey. * @exception IOException * if the channel interest set cannot be configured. */ /* package */ void addInterest(final AsyncChannel achannel, final int interestOps) throws IOException { Objects.requireNonNull(achannel, "achannel is null"); if (Thread.currentThread() == mSelectorThread) { doAddInterest(achannel.channel(), interestOps); } else { // Add a new runnable to the list of tasks to be executed // in the selector thread invokeLater(() -> { try { doAddInterest( achannel.channel(), interestOps); } catch (IOException ex) { achannel.handleError(ex); } }); } return; } // end of addInterest(AsyncChannel, int) /** * Removes an interest from the list of events where a * channel is registered. The associated event handler will * stop receiving events for the specified interest. * @param achannel the channel to be updated. Must be * registered. * @param interestOps the interest to be removed. Should be * one of the constants defined on SelectionKey. * @exception IOException * if the channel interest set cannot be configured. */ /* package */ void removeInterest(final AsyncChannel achannel, final int interestOps) throws IOException { Objects.requireNonNull(achannel, "achannel is null"); if (Thread.currentThread() == mSelectorThread) { doRemoveInterest(achannel.channel(), interestOps); } else { invokeLater(() -> { try { doRemoveInterest( achannel.channel(), interestOps); } catch (IOException ioex) { achannel.handleError(ioex); } }); } return; } // end of removeInterest(AsyncChannel, int) /** * Executes the given task synchronously in the selector * thread. This method schedules the task, waits for its * execution and only then returns. * @param task the task to be executed on the selector's * thread. * @throws InterruptedException * if this thread is interrupted while waiting for the task * to complete. */ /* package */ void invokeAndWait(final Runnable task) throws InterruptedException { if (Thread.currentThread() == mSelectorThread) { // We are in the selector's thread. No need to // schedule execution. task.run(); } else { // Used to deliver the notification that the task is // executed. final CountDownLatch latch = new CountDownLatch(1); // Uses the invokeLater method with a newly created // task. invokeLater(new SelectorTask(task, latch)); // Wait for the task to complete. latch.await(); // Ok, we are done, the task was executed. Proceed. } return; } // end of invokeAndWait(Runnable) /** * Updates the interest set associated with a selection key. * The old interest is discarded, being replaced by the new * one. * @param key The key to be updated. * @param interestOps register interest in these operations. * @throws IOException * if {@code key} is canceled. */ private void changeKeyInterest(final SelectionKey key, final int interestOps) throws IOException { // This method might throw two unchecked exceptions: // 1. IllegalArgumentException - Should never happen. // It is a bug if it happens // 2. CancelledKeyException - Might happen if the channel // is closed while a packet is being dispatched. try { key.interestOps(interestOps); } catch (CancelledKeyException cancex) { throw ( new IOException( "change channel interest failed.", cancex)); } return; } // end of changeKeyInterest(SelectionKey, int) /** * Executes all tasks queued for execution on the selector's * thread. */ private void doInvocations() { Iterator it; // Acquire the pending lock if: // 1. there are no registered selector keys or // 2. there are pending invocations. // Note: because this method is called only when in the // selector thread, no synchronization is necessary to // check the the selector key set. // Why not? Selector key set is thread unsafe. // Because the select key set is only accessed from the // selector thread. if (((mSelector.keys()).isEmpty() || mPendingFlag) && mContinueFlag) { mPendingLock.lock(); try { // If there are no selection keys and no pending // tasks, then wait for some tasks to show up. if (mSelector.keys().isEmpty()) { if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest( "SelectorThread: waiting for tasks."); } while (mPendingInvocations.isEmpty() && mContinueFlag) { try { mNotEmpty.await(); } catch (InterruptedException interrupt) { // Ignore. } } } if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest( String.format( "SelectorThread: %,d tasks to run.", mPendingInvocations.size())); } for (it = mPendingInvocations.iterator(); it.hasNext() && mContinueFlag; ) { // Don't let one tasks failure ruin it // for the remaining tasks. try { (it.next()).run(); } catch (Exception ex) { sLogger.log( Level.FINEST, "select task failed", ex); } } mPendingInvocations.clear(); mPendingFlag = false; } finally { mPendingLock.unlock(); } } return; } // end of doInvocations() /** * Executes the given task in the selector thread. This * method returns as soon as the task is scheduled, without * waiting for the task to be executed. * @param task schedule this task for execution later. */ private void invokeLater(final Runnable task) { mPendingLock.lock(); try { mPendingInvocations.add(task); mPendingFlag = true; mNotEmpty.signal(); } finally { mPendingLock.unlock(); } mSelector.wakeup(); } // end of invokeLater(Runnable) /** * Registers the AsyncChannel's selectable channel with the * selector. * @param achannel register operations with this channel. * @param interestOps register interest in these operations. * @throws IOException * if the selectable channel is either {@code null} or * closed. */ private void doRegister(final AsyncChannel achannel, final int interestOps) throws IOException { final SelectableChannel channel = achannel.channel(); // Normally argument validation is done by the method // calling a private method but in this case this method // may be called asynchronously by posting it to the // selector thread. That means between the posting and // now, the channel connection status could change. So // making the check in the original register() method // is meaningless. if (!channel.isOpen()) { throw (new IOException("Channel is not open.")); } final Selector selector = mSelector.selector(); if (sLogger.isLoggable(Level.FINE)) { sLogger.fine( String.format( "SelectorThread: registering channel (ops=%s).", outputOps(interestOps))); } if (channel.isRegistered()) { final SelectionKey key = channel.keyFor(selector); key.interestOps(interestOps); key.attach(achannel); } else { channel.configureBlocking(false); channel.register( selector, interestOps, achannel); } return; } // end of doRegister(AsyncChannel, int) /** * Performs the actual work of updating a selectable * channel's interest set. * @param channel add interest to this channel. * @param interestOps add these operations. * @throws IOException * if there is an I/O error updating the channel interest * set. */ private void doAddInterest(final SelectableChannel channel, final int interestOps) throws IOException { final Selector selector = mSelector.selector(); SelectionKey key; // If the channel is closed, then either channel or key // will be null. if ((key = channel.keyFor(selector)) != null) { final int ops = (key.interestOps() | interestOps); if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest( String.format( "SelectorThread: setting interest ops %s.", outputOps(ops))); } changeKeyInterest(key, ops); } return; } // end of doAddInterest(SelectableChannel, int) /** * Performs the actual work of updating a selectable * channel's interest set. * @param channel remove interest set from this channel. * @param interestOps remove these operations. * @throws IOException * if there is an I/O error updating the channel interest * set. */ private void doRemoveInterest(final SelectableChannel channel, final int interestOps) throws IOException { final Selector selector = mSelector.selector(); SelectionKey key; // If the channel is closed, then either channel or key // will be null. if ((key = channel.keyFor(selector)) != null) { final int ops = (key.interestOps() & ~interestOps); if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest( String.format( "SelectorThread: setting interest ops %s.", outputOps(ops))); } changeKeyInterest(key, ops); } return; } // end of doRemoveInterest(SelectableChannel, int) /** * Closes all channels registered with the selector. Used to * clean up when the selector dies and cannot be recovered. */ private void closeSelectorAndChannels() { mSelector.keys().stream(). forEach((key) -> { try { key.channel().close(); } catch (IOException e) { // Ignore } }); try { mSelector.close(); } catch (IOException e) { // Ignore } } // end of closeSelectorAndChannels() /** * Returns text matching the interest operations bit set. * @param interestOps convert this bit set to text. * @return human-readable text matching the interest ops. */ private static String outputOps(final int interestOps) { final Formatter retval = new Formatter(); if ((interestOps & ALL_OPS) == 0) { retval.format("none"); } else { int index; String sep = ""; for (index = 0; index < OPS.length; ++index) { if ((interestOps & OPS[index]) != 0) { retval.format("%s%s", sep, OP_NAMES[index]); sep = ","; } } } return (retval.toString()); } // end of outputOps(int) // Given an integer x, find the power of 2 such that // 2**(n -1 ) < x <= 2**n. // See http://stackoverflow.com/questions/364985/algorithm-for-finding-the-smallest-power-of-two-thats-greater-or-equal-to-a-give // private static int pow2roundup(final int x) // { // int y = x; // int retval = BLOCK_SIZE; // // if (x > BLOCK_SIZE) // { // --y; // y |= (y >> SHIFT_1); // y |= (y >> SHIFT_2); // y |= (y >> SHIFT_4); // y |= (y >> SHIFT_8); // y |= (y >> SHIFT_16); // // retval = (y + 1); // } // // return (retval); // } // end of pow2roundup(int) //--------------------------------------------------------------- // Inner classes. // /** * Encapsulates a selector task so it may be executed on the * selector thread. */ private static class SelectorTask implements Runnable { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * The actual task which is run. */ private final Runnable mTask; /** * Signal to selector thread when the task is run. */ private final CountDownLatch mLatch; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new selector task instance for the given * subtask and signal. * @param task the subtask actually run. * @param latch used to signal the selector thread when * the task is run. */ public SelectorTask(Runnable task, CountDownLatch latch) { mTask = task; mLatch = latch; } // end of SelectorTask(Runnable, CountDownLatch) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Runnable Interface Implementation. // /** * Runs the subtask and then decrements the latch to let * the selector thread know when the task is finished. */ @Override public void run() { try { mTask.run(); } finally { mLatch.countDown(); } } // end of run() // // end of Runnable Interface Implementation. //------------------------------------------------------- } // end of clas SelectorTask } // endof class SelectorThread




© 2015 - 2024 Weber Informatics LLC | Privacy Policy