package org.playorm.nio.impl.cm.basic; import java.io.IOException; import java.net.PortUnreachableException; import java.nio.ByteBuffer; import java.nio.channels.CancelledKeyException; import java.nio.channels.NotYetConnectedException; import java.nio.channels.SelectionKey; import java.util.Iterator; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.playorm.nio.api.handlers.ConnectionListener; import org.playorm.nio.api.handlers.DataListener; import org.playorm.nio.api.testutil.nioapi.Select; import org.playorm.nio.impl.util.DataChunkImpl; import org.playorm.nio.impl.util.ProcessedListener; final class Helper { private static final Logger apiLog = Logger.getLogger(DataListener.class.getName()); private static final Logger log = Logger.getLogger(Helper.class.getName()); //private static BufferHelper helper = ChannelManagerFactory.bufferHelper(null); private static boolean logBufferNextRead = false; private static BufferPool pool = new BufferPool(); private Helper() {} public static String opType(int ops) { String retVal = ""; if((ops & SelectionKey.OP_ACCEPT) > 0) retVal+="A"; if((ops & SelectionKey.OP_CONNECT) > 0) retVal+="C"; if((ops & SelectionKey.OP_READ) > 0) retVal+="R"; if((ops & SelectionKey.OP_WRITE) > 0) retVal+="W"; return retVal; } public static void processKeys(Object id, Set<SelectionKey> keySet, SelectorManager2 mgr) { Iterator<SelectionKey> iter = keySet.iterator(); while (iter.hasNext()) { SelectionKey key = null; try { key = iter.next(); if(log.isLoggable(Level.FINE)) log.fine(key.attachment()+" ops="+Helper.opType(key.readyOps()) +" acc="+key.isAcceptable()+" read="+key.isReadable()+" write"+key.isWritable()); processKey(id, key, mgr); // } catch(CancelledKeyException e) { // log.log(Level.INFO, "Cancelled key may be normal", e); } catch(IOException e) { log.log(Level.WARNING, id+""+key.attachment()+"Processing of key failed, closing channel", e); try { if(key != null) key.channel().close(); } catch(Throwable ee) { log.log(Level.WARNING, id+""+key.attachment()+"Close of channel failed", ee); } } catch(CancelledKeyException e) { //TODO: get rid of this if...else statement by fixing //CancelledKeyException on linux so the tests don't fail log.log(Level.FINE, id+""+key.attachment()+"Processing of key failed, but continuing channel manager loop", e); } catch(Throwable e) { log.log(Level.WARNING, id+""+key.attachment()+"Processing of key failed, but continuing channel manager loop", e); try { key.cancel(); } catch(Throwable ee) {} } } //clear the whole keySet as we processed them all in the while loop. //If you do not clear the keySet, keys that have been already processed stay //in the selected Key set. If another key gets added to this set, the selector //goes off again and has the stale key plus the new key and the stale key //is processed again. keySet.clear(); } private static void processKey(Object id, SelectionKey key, SelectorManager2 mgr) throws IOException, InterruptedException { if(log.isLoggable(Level.FINEST)) log.finest(id+""+key.attachment()+"proccessing"); //This is code to try to avoid the CancelledKeyExceptions if(!key.channel().isOpen() || !key.isValid()) return; //if isAcceptable, than is a ServerSocketChannel if(key.isAcceptable()) { Helper.acceptSocket(id, key); } if(key.isConnectable()) Helper.connect(id, key); if(key.isWritable()) { Helper.write(id, key); } //The read MUST be after the write as a call to key.isWriteable is invalid if the //read resulted in the far end closing the socket. if(key.isReadable()) { Helper.read(id, key, mgr); } } //each of these functions should be a handler for a new type that we set up //on the outside of this thing. The signature is the same thing every time // and we pass key and the Channel. We can cast to the proper one. private static void acceptSocket(Object id, SelectionKey key) throws IOException { // SelectableChannel s = key.channel(); if(log.isLoggable(Level.FINER)) log.finer(id+""+key.attachment()+"Incoming Connection="+key); WrapperAndListener struct = (WrapperAndListener)key.attachment(); ConnectionListener cb = struct.getAcceptCallback(); BasTCPServerChannel channel = (BasTCPServerChannel)struct.getChannel(); channel.accept("session "+channel.getSession(), cb); } private static void connect(Object id, SelectionKey key) throws IOException { if(log.isLoggable(Level.FINEST)) log.finest(id+""+key.attachment()+"finishing connect process"); WrapperAndListener struct = (WrapperAndListener)key.attachment(); ConnectionListener callback = struct.getConnectCallback(); BasTCPChannel channel = (BasTCPChannel)struct.getChannel(); //must change the interests to not interested in connect anymore //since we are connected otherwise selector seems to keep firing over //and over again with 0 keys wasting cpu like a while(true) loop int interests = key.interestOps(); key.interestOps(interests & (~SelectionKey.OP_CONNECT)); try { channel.finishConnect(); callback.connected(channel); } catch(Exception e) { log.log(Level.WARNING, id+""+key.attachment()+"Could not open connection", e); callback.failed(channel, e); } } private static void read(Object id, SelectionKey key, SelectorManager2 mgr) throws IOException { if(log.isLoggable(Level.FINEST)) log.finest(id+""+key.attachment()+"reading data"); WrapperAndListener struct = (WrapperAndListener)key.attachment(); DataListener in = struct.getDataHandler(); BasChannelImpl channel = (BasChannelImpl)struct.getChannel(); ProcessedListenerImpl l = new ProcessedListenerImpl(channel, in, mgr); DataChunkImpl chunk = pool.nextBuffer(id, l); ByteBuffer b = chunk.getData(); try { if(logBufferNextRead) log.info(channel+"buffer="+b); int bytes = channel.readImpl(b); if(logBufferNextRead) { logBufferNextRead = false; log.info(channel+"buffer2="+b); } processBytes(id, key, chunk, bytes, mgr); } catch(PortUnreachableException e) { //this is a normal occurence when some writes out udp to a port that is not //listening for udp!!! log as finer and fire to client to deal with it. log.log(Level.FINEST, id+"Client sent data to a host or port that is not listening " + "to udp, or udp can't get through to that machine", e); in.failure(channel, null, e); } catch(NotYetConnectedException e) { //this happens in udp when I disconnect after someone had already been streaming me //data. It is supposed to stop listening but selector keeps firing. log.log(Level.WARNING, id+"Can't read until UDPChannel is connected", e); in.failure(channel, null, e); } catch(IOException e) { //kept getting the following exception so I added this as protection //NOTE: this exceptionn should never occur. read should be returning -1 //but for some reason is returning hte exception instead. // WARNING: [server] Processing of key failed, closing channel // java.io.IOException: An established connection was aborted by the software in your host machine // at sun.nio.ch.SocketDispatcher.read0(Native Method) // at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:25) // at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:233) // at sun.nio.ch.IOUtil.read(IOUtil.java:206) // at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:207) // at biz.xsoftware.impl.nio.cm.basic.TCPChannelImpl.readImpl(TCPChannelImpl.java:168) // at biz.xsoftware.impl.nio.cm.basic.Helper.read(Helper.java:128) // at biz.xsoftware.impl.nio.cm.basic.Helper.processKey(Helper.java:77) // at biz.xsoftware.impl.nio.cm.basic.Helper.processKeys(Helper.java:43) // at biz.xsoftware.impl.nio.cm.basic.SelectorManager2.runLoop(SelectorManager2.java:262) // at biz.xsoftware.impl.nio.cm.basic.SelectorManager2$PollingThread.run // (SelectorManager2.java:224) //another one that landes here is the Connection reset by peer".... // Jan 18, 2012 1:00:42 PM biz.xsoftware.impl.nio.cm.basic.Helper read // INFO: [stserver] Exception // java.io.IOException: Connection reset by peer // at sun.nio.ch.FileDispatcher.read0(Native Method) // at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39) // at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:251) // at sun.nio.ch.IOUtil.read(IOUtil.java:224) // at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:254) // at biz.xsoftware.impl.nio.cm.basic.chanimpl.SocketChannelImpl.read(SocketChannelImpl.java:65) // at biz.xsoftware.impl.nio.cm.basic.BasTCPChannel.readImpl(BasTCPChannel.java:108) // at biz.xsoftware.impl.nio.cm.basic.Helper.read(Helper.java:162) // at biz.xsoftware.impl.nio.cm.basic.Helper.processKey(Helper.java:104) // at biz.xsoftware.impl.nio.cm.basic.Helper.processKeys(Helper.java:51) // at biz.xsoftware.impl.nio.cm.basic.SelectorManager2.selectorFired(SelectorManager2.java:253) // at biz.xsoftware.impl.nio.cm.basic.nioimpl.SelectorImpl.runLoop(SelectorImpl.java:126) // at biz.xsoftware.impl.nio.cm.basic.nioimpl.SelectorImpl$PollingThread.run(SelectorImpl.java:107) //One other exception starts with "An existing connection was forcibly closed" //in the case of SSL over TCP only, sometimes instead of reading off a -1, this will //throw an IOException: "An existing connection was forcibly closed by the remote host" //we also close UDPChannels as well on IOException. Not sure if this is good or not. String msg = e.getMessage(); if(msg != null && (msg.startsWith("An existing connection was forcibly closed") || msg.startsWith("Connection reset by peer") || msg.startsWith("An established connection was aborted by the software in your host machine"))) { log.log(Level.FINE, id+"Exception 2", e); processBytes(id, key, chunk, -1, mgr); } else { log.log(Level.WARNING, id+"IO Exception unexpected", e); in.failure(channel, null, e); } } } /** * @param id * @param b * @param bytes * @param mgr * @throws IOException */ private static void processBytes(Object id, SelectionKey key, DataChunkImpl chunk, int bytes, SelectorManager2 mgr) throws IOException { WrapperAndListener struct = (WrapperAndListener)key.attachment(); DataListener in = struct.getDataHandler(); BasChannelImpl channel = (BasChannelImpl)struct.getChannel(); ByteBuffer b = chunk.getData(); //in 1.5.0_08, was getting a nullpointer on helper... b.flip(); //helper.doneFillingBuffer(b); if(bytes < 0) { if(apiLog.isLoggable(Level.FINE)) apiLog.fine(channel+"far end closed, cancel key, close socket"); channel.closeOnSelectorThread(); in.farEndClosed(channel); } else if(bytes > 0) { //let's DEregister for read until this packet is processed and re-register when they set the chunk to processed(true) unregisterChannelForReads(mgr, channel); if(apiLog.isLoggable(Level.FINER)) apiLog.finer(channel+"READ bytes="+bytes); in.incomingData(channel, chunk); } } private static void unregisterChannelForReads(SelectorManager2 mgr, BasChannelImpl channel) { try { mgr.unregisterChannelForRead(channel); } catch (IOException e) { log.log(Level.WARNING, "Exception on unregister for read", e); } catch (InterruptedException e) { log.log(Level.WARNING, "exception on unregsiter", e); } } private static class ProcessedListenerImpl implements ProcessedListener { private SelectorManager2 mgr; private BasChannelImpl channel; private DataListener in; public ProcessedListenerImpl(BasChannelImpl channel, DataListener in, SelectorManager2 mgr) { this.mgr = mgr; this.channel = channel; this.in = in; } @Override public void processed(DataChunkImpl chunk) { try { mgr.registerSelectableChannel(channel, SelectionKey.OP_READ, in, false); } catch (IOException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } } } private static void write(Object id, SelectionKey key) throws IOException, InterruptedException { if(log.isLoggable(Level.FINEST)) log.finest(key.attachment()+"writing data"); WrapperAndListener struct = (WrapperAndListener)key.attachment(); BasChannelImpl channel = (BasChannelImpl)struct.getChannel(); if(log.isLoggable(Level.FINER)) log.finer(channel+"notifying channel of write"); channel.writeAll(); } static void unregisterSelectableChannel(RegisterableChannelImpl channel, int ops) { SelectorManager2 mgr = channel.getSelectorManager(); if(!Thread.currentThread().equals(mgr.getThread())) throw new RuntimeException(channel+"Bug, changing selector keys can only be done " + "on registration thread because there is not synchronization"); //this could be dangerous and result in deadlock....may want //to move this to the selector thread from jdk bugs!!! //but alas, follow KISS, move on... Select select = channel.getSelectorManager().getSelector(); SelectionKey key = channel.keyFor(select); if(key == null || !key.isValid()) //no need to unregister, key is cancelled return; int previous = key.interestOps(); int opsNow = previous & ~ops; //subtract out the operation key.interestOps(opsNow); //make sure we remove the appropriate listener and clean up if(key.attachment() != null) { WrapperAndListener struct = (WrapperAndListener)key.attachment(); struct.removeListener(ops); } } }