/* Copyright (c) 2002, Dean Hiller All rights reserved. ***************************************************************** IF YOU MAKE CHANGES TO THIS CODE AND DO NOT POST THEM, YOU WILL BE IN VIOLATION OF THE LICENSE I HAVE GIVEN YOU. Contact me at deanhiller@users.sourceforge.net if you need a different license. ***************************************************************** 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.playorm.nio.impl.cm.basic; import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.event.EventListenerList; import org.playorm.nio.api.handlers.ConnectionListener; import org.playorm.nio.api.handlers.DataListener; import org.playorm.nio.api.testutil.nioapi.ChannelRegistrationListener; import org.playorm.nio.api.testutil.nioapi.Select; import org.playorm.nio.api.testutil.nioapi.SelectorListener; import org.playorm.nio.api.testutil.nioapi.SelectorProviderFactory; import org.playorm.nio.api.testutil.nioapi.SelectorRunnable; public class SelectorManager2 implements SelectorListener { //-------------------------------------------------------------------- // FIELDS/MEMBERS //-------------------------------------------------------------------- private static final Logger log = Logger.getLogger(SelectorManager2.class.getName()); private Select selector; private SelectorProviderFactory factory; private Object id; private EventListenerList listenerList = new EventListenerList(); //flag for logs so we know that the selector was woken up to close a socket. //this is needed as we want to see in the logs the reason of every selector fire clearly private boolean needCloseOrRegister; private boolean stopped; //-------------------------------------------------------------------- // CONSTRUCTORS //-------------------------------------------------------------------- public SelectorManager2(SelectorProviderFactory factory, Object id) { this.id = id; this.factory = factory; } //-------------------------------------------------------------------- // BUSINESS METHODS //-------------------------------------------------------------------- public synchronized void start() throws IOException { selector = factory.provider(id+""); selector.startPollingThread(this); selector.setRunning(true); } /** * @throws IOException * @throws InterruptedException * */ public synchronized void stop() throws IOException, InterruptedException { try { if(!isRunning()) return; stopped = true; selector.stopPollingThread(); } catch(Throwable e) { //there is nothing a client can do to a recover from this so swallow it for them log.log(Level.WARNING, "Exception stopping selector", e); } } Select getSelector() { return selector; } public void registerServerSocketChannel(BasTCPServerChannel s, ConnectionListener listener) throws IOException, InterruptedException { waitForRegister(s, SelectionKey.OP_ACCEPT, listener, true); } public void registerChannelForConnect(final RegisterableChannelImpl s, final ConnectionListener listener) throws IOException, InterruptedException { registerSelectableChannel(s, SelectionKey.OP_CONNECT, listener, true); } public void registerChannelForRead(final RegisterableChannelImpl s, final DataListener listener) throws IOException, InterruptedException { registerSelectableChannel(s, SelectionKey.OP_READ, listener, true); } public void unregisterChannelForRead(BasChannelImpl c) throws IOException, InterruptedException { unregisterSelectableChannel(c, SelectionKey.OP_READ); } private void unregisterSelectableChannel(RegisterableChannelImpl channel, int ops) throws IOException, InterruptedException { if(stopped) return; //do nothing if stopped else if(!isRunning()) throw new IllegalStateException("ChannelMgr is not running, call ChannelManager.starimport " + "biz.xsoftware.api.nio.test.nioapi.SelectKey;t first"); else if(Thread.currentThread().equals(selector.getThread())) Helper.unregisterSelectableChannel(channel, ops); else asynchUnregister(channel, ops); } void registerSelectableChannel(final RegisterableChannelImpl s, final int validOps, final Object listener, boolean needWait) throws IOException, InterruptedException { if(stopped) return; //do nothing if stopped if(!isRunning()) throw new IllegalStateException("ChannelMgr is not running, call ChannelManager.start first"); else if(Thread.currentThread().equals(selector.getThread())) registerChannelOnThisThread(s, validOps, listener); else waitForRegister(s, validOps, listener, needWait); } private void registerChannelOnThisThread( RegisterableChannelImpl channel, int validOps, Object listener) throws ClosedChannelException { if(channel == null) throw new IllegalArgumentException("cannot register a null channel"); else if(!Thread.currentThread().equals(selector.getThread())) throw new IllegalArgumentException("This function can only be invoked on PollingThread"); else if(channel.isClosed()) return; //do nothing if the channel is closed else if(!selector.isRunning()) return; //do nothing if the selector is not running WrapperAndListener struct; SelectableChannel s = channel.getRealChannel(); int previousOps = 0; if (log.isLoggable(Level.FINEST)) log.log(Level.FINEST, channel+"registering2="+s+" ops="+Helper.opType(validOps)); SelectionKey previous = channel.keyFor(selector); if(previous == null) { struct = new WrapperAndListener(channel); }else if(previous.attachment() == null) { struct = new WrapperAndListener(channel); previousOps = previous.interestOps(); } else { struct = (WrapperAndListener)previous.attachment(); previousOps = previous.interestOps(); } struct.addListener(id, listener, validOps); int allOps = previousOps | validOps; SelectionKey key = channel.register(selector, allOps, struct); channel.setKey(key); if (log.isLoggable(Level.FINER)) log.log(Level.FINER, channel+"registered2="+s+" allOps="+Helper.opType(allOps)); } private void asynchUnregister(final RegisterableChannelImpl s, final int validOps) throws IOException, InterruptedException { if (s.isBlocking()) throw new IllegalArgumentException(s+"Only non-blocking selectable channels can be used. " + "please call SelectableChannel.configureBlocking before passing in the channel"); // in SelctorImpl.java(check sun's SCSL code), SelectorImpl grabs // a lock in select() method. This register tries to grab the same // lock, so we need to wakeup the select method here and have it do // the processing of this request so it can release the lock so // the register can grab the lock. SelectorRunnable r = new SelectorRunnable() { public void run() throws ClosedChannelException { Helper.unregisterSelectableChannel(s, validOps); } public String toString() { return Helper.opType(validOps); } }; ChannelRegistrationListener regRequest = selector.createRegistrationListener(s, r, this); listenerList.add(ChannelRegistrationListener.class, regRequest); regRequest.waitForFinish(true); } private void waitForRegister(final RegisterableChannelImpl s, final int validOps, final Object listener, boolean needWait) throws IOException, InterruptedException { if(s.isBlocking()) throw new IllegalArgumentException(s+"Only non-blocking selectable channels can be used. " + "please call SelectableChannel.configureBlocking before passing in the channel"); //in SelectorImpl.java(check sun's SCSL code), SelectorImpl grabs //a lock in select() method. This register tries to grab the same //lock, so we need to wakeup the select method here and have it do //the processing of this request so it can release the lock so //the register can grab the lock. SelectorRunnable r = new SelectorRunnable() { public void run() throws ClosedChannelException { try { registerChannelOnThisThread(s, validOps, listener); } catch (Exception e) { throw new RuntimeException(s+"Exception", e); } } public String toString() { return Helper.opType(validOps); } }; ChannelRegistrationListener regRequest = selector.createRegistrationListener(s, r, this); listenerList.add(ChannelRegistrationListener.class, regRequest); regRequest.waitForFinish(needWait); } public void selectorFired() { //NOTE: we have to process registrations as close to waitOnSelector as possible. //otherwise, we could end up waiting on the selector when there are registrations to //process!!!! //currently, this processes the registration requests. fireToListeners(); waitOnSelector(); //processKeys may will go through all keys and do some work, but //may not do all work it could on each key. This is to keep from any //client starving and from registrations starving in the case where one //client just overloads the server and keeps sending a stream of data. //he will be served with all the others in an equal priority round robin //fashion. Set<SelectionKey> keySet = selector.selectedKeys(); if(log.isLoggable(Level.FINER)) log.finer(id+"keySetCnt="+keySet.size()+" registerCnt="+listenerList.getListenerCount() +" needCloseOrRegister="+needCloseOrRegister+" wantShutdown="+selector.isWantShutdown()); needCloseOrRegister = false; if(keySet.size() > 0) { Helper.processKeys(id, keySet, this); } } protected int waitOnSelector() { int numNewKeys = 0; try { if(log.isLoggable(Level.FINER)) log.finer(id+"coming into select"); numNewKeys = selector.select(); //should assert we are not stopping, have listeners, or have keys to process... if(log.isLoggable(Level.FINER)) log.finer(id+"coming out of select with newkeys="+numNewKeys+ " regCnt="+listenerList.getListenerCount()+" needCloseOrRegister="+needCloseOrRegister+ " wantShutdown="+selector.isWantShutdown()); assert numNewKeys > 0 || listenerList.getListenerCount() > 0 || selector.isWantShutdown() : "Should only wakeup when we have stuff to do"; } catch (IOException e) { log.log(Level.WARNING, id+"Having trouble with a channel", e); } return numNewKeys; } private void fireToListeners() { ChannelRegistrationListener[] listeners = listenerList.getListeners(ChannelRegistrationListener.class); for(int i = 0; i < listeners.length; i++) { listenerList.remove(ChannelRegistrationListener.class, listeners[i]); listeners[i].processRegistrations(); } } /** * Unfortunately, previously, a registered Socket cannot close if the PollingThread is * hung on the selector. A registered socket is closed on entry to the * select or selectNow function, therefore, we just wake up the selector * so we reenter him to close the socket. * * Also, this is used to wakeup the selector to process registrations!!! */ public void wakeUpSelector() { if(log.isLoggable(Level.FINE)) log.fine(id+"Wakeup selector to enable close or registers"); needCloseOrRegister = true; selector.wakeup(); } public Object getThread() { return selector.getThread(); } public boolean isRunning() { if(selector == null) return false; return selector.isRunning(); } }