/*
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.webpieces.nio.impl.cm.basic;
import java.io.IOException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedDeque;
import org.webpieces.util.logging.Logger;
import org.webpieces.util.logging.LoggerFactory;
import org.webpieces.data.api.BufferPool;
import org.webpieces.nio.api.channels.Channel;
import org.webpieces.nio.api.handlers.ConnectionListener;
import org.webpieces.nio.api.handlers.DataListener;
import org.webpieces.nio.api.testutil.nioapi.Select;
import org.webpieces.nio.api.testutil.nioapi.SelectorListener;
import org.webpieces.nio.api.testutil.nioapi.SelectorProviderFactory;
import org.webpieces.nio.impl.cm.basic.nioimpl.ChannelRegistrationListener;
public class SelectorManager2 implements SelectorListener {
//--------------------------------------------------------------------
// FIELDS/MEMBERS
//--------------------------------------------------------------------
private static final Logger log = LoggerFactory.getLogger(SelectorManager2.class);
private Select selector;
private SelectorProviderFactory factory;
private ConcurrentLinkedDeque<ChannelRegistrationListener> listenerList = new ConcurrentLinkedDeque<>();
//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;
private BufferPool pool;
private String threadName;
//--------------------------------------------------------------------
// CONSTRUCTORS
//--------------------------------------------------------------------
public SelectorManager2(SelectorProviderFactory factory, BufferPool pool, String threadName) {
this.factory = factory;
this.pool = pool;
this.threadName = threadName;
}
//--------------------------------------------------------------------
// BUSINESS METHODS
//--------------------------------------------------------------------
public synchronized void start() {
selector = factory.provider();
selector.startPollingThread(this, threadName);
selector.setRunning(true);
}
/**
*/
public synchronized void stop() {
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.error("Exception stopping selector", e);
}
}
Select getSelector() {
return selector;
}
public CompletableFuture<Void> registerServerSocketChannel(BasTCPServerChannel s, ConnectionListener listener)
throws IOException, InterruptedException {
return asyncRegister(s, SelectionKey.OP_ACCEPT, listener);
}
public CompletableFuture<Channel> registerChannelForConnect(final RegisterableChannelImpl s)
throws IOException, InterruptedException {
CompletableFuture<Channel> future = new CompletableFuture<Channel>();
registerSelectableChannel(s, SelectionKey.OP_CONNECT, future);
return future;
}
public CompletableFuture<Void> registerChannelForRead(final RegisterableChannelImpl s, final DataListener listener)
throws IOException, InterruptedException {
return registerSelectableChannel(s, SelectionKey.OP_READ, listener);
}
public CompletableFuture<Void> unregisterChannelForRead(BasChannelImpl c) throws IOException, InterruptedException {
return unregisterSelectableChannel(c, SelectionKey.OP_READ);
}
private CompletableFuture<Void> unregisterSelectableChannel(RegisterableChannelImpl channel, int ops)
throws IOException, InterruptedException {
if(stopped)
return CompletableFuture.completedFuture(null); //do nothing if stopped
else if(!isRunning())
throw new IllegalStateException("ChannelMgr is not running, call ChannelManager.start first");
else if(Thread.currentThread().equals(selector.getThread())) {
Helper.unregisterSelectableChannel(channel, ops);
return CompletableFuture.completedFuture(null);
} else
return asynchUnregister(channel, ops);
}
CompletableFuture<Void> registerSelectableChannel(final RegisterableChannelImpl s, final int validOps, final Object listener) {
if(stopped) {
CompletableFuture<Void> future = new CompletableFuture<Void>();
future.completeExceptionally(new IllegalStateException("This chanMgr is stopped")); //do nothing if stopped
return future;
} else if(!isRunning())
throw new IllegalStateException("ChannelMgr is not running, call ChannelManager.start first");
else if(Thread.currentThread().equals(selector.getThread())) {
registerChannelOnThisThread(s, validOps, listener);
return CompletableFuture.completedFuture(null);
} else
return asyncRegister(s, validOps, listener);
}
private void registerChannelOnThisThread(
RegisterableChannelImpl channel, int validOps, Object listener) {
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;
log.trace(()->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(listener, validOps);
int allOps = previousOps | validOps;
SelectionKey key = channel.register(selector, allOps, struct);
channel.setKey(key);
//log.info("registering="+Helper.opType(allOps)+" opsToAdd="+Helper.opType(validOps)+" previousOps="+Helper.opType(previousOps)+" type="+type);
//log.info(channel+"registered2="+s+" allOps="+Helper.opType(allOps)+" k="+Helper.opType(key.interestOps()));
log.trace(()->channel+"registered2="+s+" allOps="+Helper.opType(allOps));
}
private CompletableFuture<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");
//log.info("asyn UNregister="+Helper.opType(validOps));
CompletableFuture<Void> future = new CompletableFuture<Void>();
//This is a 12 year old statement and maybe not true anymore...
// 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.
ChannelRegistrationListener r = new ChannelRegistrationListener(future, validOps) {
public void run() {
Helper.unregisterSelectableChannel(s, validOps);
}
};
listenerList.add(r);
log.trace(()->s+"call wakeup on selector to register for="+r);
wakeUpSelector();
return future;
}
private CompletableFuture<Void> asyncRegister(
final RegisterableChannelImpl s, final int validOps, final Object listener) {
if(s.isBlocking())
throw new IllegalArgumentException(s+"Only non-blocking selectable channels can be used. " +
"please call SelectableChannel.configureBlocking before passing in the channel");
CompletableFuture<Void> future = new CompletableFuture<Void>();
//This is a 12 year old statement and maybe not true anymore...
//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.
ChannelRegistrationListener r = new ChannelRegistrationListener(future, validOps) {
@Override
public void run() {
// if((validOps & SelectionKey.OP_READ) > 0)
// log.info("really registering for reads");
registerChannelOnThisThread(s, validOps, listener);
}
};
listenerList.add(r);
log.trace(()->s+"call wakeup on selector to register for="+r);
wakeUpSelector();
return future;
}
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();
log.trace(()->"keySetCnt="+keySet.size()+" registerCnt="+listenerList.size()
+" needCloseOrRegister="+needCloseOrRegister+" wantShutdown="+selector.isWantShutdown());
needCloseOrRegister = false;
if(keySet.size() > 0) {
Helper.processKeys(keySet, this, pool);
}
}
protected int waitOnSelector() {
int numNewKeys = 0;
log.trace(()->"coming into select");
numNewKeys = selector.select();
//should assert we are not stopping, have listeners, or have keys to process...
final int num = numNewKeys;
log.trace(()->"coming out of select with newkeys="+num+
" regCnt="+listenerList.size()+" needCloseOrRegister="+needCloseOrRegister+
" wantShutdown="+selector.isWantShutdown());
// assert numNewKeys > 0 || listenerList.getListenerCount() > 0 || selector.isWantShutdown() :
// "Should only wakeup when we have stuff to do";
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();
// }
while(!listenerList.isEmpty()) {
ChannelRegistrationListener l = listenerList.poll();
l.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() {
log.trace(()->"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();
}
}