net.sf.eBus.net.SelectorThread Maven / Gradle / Ivy
/*
(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:
*
* -
* 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.
*
* -
* 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.
*
*
*
* @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