package com.netifera.platform.net.sockets.internal; import java.io.IOException; import java.net.SocketTimeoutException; import java.nio.channels.CancelledKeyException; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import com.netifera.platform.api.log.ILogger; import com.netifera.platform.net.sockets.AsynchronousSelectableChannel; /** * A <code>SelectionContext</code> object encapsulates all the state of a * socket under control of the socket engine */ class SelectionContext { private final SocketEngineService engine; private final AsynchronousSelectableChannel asynchronousChannel; private final SelectableChannel socket; private final ILogger logger; private final Queue<SelectionFuture<Void,?>> connectQueue = new ArrayBlockingQueue<SelectionFuture<Void,?>>(100); private final Queue<SelectionFuture<?,?>> readQueue = new ArrayBlockingQueue<SelectionFuture<?,?>>(100); private final BlockingQueue<SelectionFuture<Integer,?>> writeQueue = new ArrayBlockingQueue<SelectionFuture<Integer,?>>(100); SelectionContext(SocketEngineService engine, AsynchronousSelectableChannel channel, ILogger logger) { this.engine = engine; this.asynchronousChannel = channel; this.socket = channel.getWrappedChannel(); this.logger = logger; } synchronized void enqueueConnect(SelectionFuture<Void,?> future) { // System.err.println("enqueue connect "+future+", #"+(connectQueue.size()+1)); connectQueue.add(future); } synchronized void enqueueRead(SelectionFuture<?,?> future) { // System.err.println("enqueue read "+future+", #"+(readQueue.size()+1)); readQueue.add(future); } synchronized void enqueueWrite(SelectionFuture<Integer,?> future) { // System.err.println("enqueue write "+future+", #"+(writeQueue.size()+1)); try { if(!writeQueue.offer(future, 2, TimeUnit.SECONDS)) { logger.warning("Failed to place write on queue for channel: " + asynchronousChannel); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("op:"); boolean opAdded = false; if (connectQueue.peek() != null) { opAdded = true; sb.append("CONNECT"); } if (readQueue.peek() != null) { if (opAdded) { sb.append('|'); } opAdded = true; sb.append("READ"); } if (writeQueue.peek() != null) { if (opAdded) { sb.append('|'); } opAdded = true; sb.append("WRITE"); } if (!opAdded) { sb.append("NONE"); } return sb.toString(); } public synchronized int getInterestOps() { int interestOps = 0; if (connectQueue.peek() != null) interestOps |= SelectionKey.OP_CONNECT; if (readQueue.peek() != null) interestOps |= SelectionKey.OP_READ; if (writeQueue.peek() != null) interestOps |= SelectionKey.OP_WRITE; return interestOps; } synchronized boolean register() { boolean registered = false; try { // System.out.println("register "+socket+" interest ops "+getInterestOps()); // SelectionKey key = socket.keyFor(engine.getSelector()); // if (key != null && key.isValid()) // key.interestOps(getInterestOps()); // else if (getInterestOps() != 0) { socket.register(engine.getSelector(), getInterestOps(), this); registered = true; } } catch (ClosedChannelException e) { // do nothing logger.error("Closed channel? "+socket, e); } catch (CancelledKeyException e) { logger.error("Canceled key for "+socket, e); close(); //XXX } return registered; } /** * Close the specified socket channel and decrease the open socket counter. * * @param channel * Socket channel to be closed. */ public synchronized void close() { try { asynchronousChannel.close(); } catch (IOException e) { logger.error("I/O error closing socket", e); } } private void handleOperation(SelectionFuture<?,?> future) { if (future != null && !future.isCancelled() && !future.isDone()) engine.getExecutor().execute(future); } /** * Test the given key to determine if the timeout period has expired. If the * connection has timed out the socket channel is closed, the key is * canceled, and then the {@link IConnectConsumer#failed()} method is called * with a {@link SocketTimeoutException}. * * @param key * Key to be tested. * @param now * Current clock value in milliseconds to compare against connect * start time. * * @throws SocketTimeoutException */ public synchronized long testTimeOut(SelectionKey key, long now) { long timeout = Long.MAX_VALUE; if (!key.isValid()) { // System.err.println("invalid key (timeouted) "+asynchronousChannel); key.cancel(); //XXX is this ok??? return timeout; } if ((key.interestOps() & SelectionKey.OP_CONNECT) != 0) { if (connectQueue.peek().testTimeOut(now)) { connectQueue.poll(); } else { timeout = Math.min(timeout, connectQueue.peek().getDeadline() - now); } } if ((key.interestOps() & SelectionKey.OP_READ) != 0) { if (readQueue.peek().testTimeOut(now)) { readQueue.poll(); } else { timeout = Math.min(timeout, readQueue.peek().getDeadline() - now); } } if ((key.interestOps() & SelectionKey.OP_WRITE) != 0) { if (writeQueue.peek().testTimeOut(now)) { writeQueue.poll(); } else { timeout = Math.min(timeout, writeQueue.peek().getDeadline() - now); } } key.interestOps(getInterestOps()); if (getInterestOps() == 0) key.cancel(); return timeout; } /** * Tests a given key to determine if it is still valid and has completed (or * failed) a connection. If the key is determined to be connectable, it is * canceled and a new thread is started for calling the connection * callbacks. * * @param key * The key to be tested. */ public synchronized void testKey(SelectionKey key) { // System.out.println("test key "+(connectQueue.peek()!=null)+" "+(readQueue.peek()!=null)+" "+(writeQueue.peek()!=null)); if (!key.isValid()) { logger.error("invalid key; closeing "+asynchronousChannel); key.cancel(); this.close(); return; } boolean connectable = key.isConnectable(); boolean readable = key.isReadable(); boolean writable = key.isWritable(); if (connectable) { handleOperation(connectQueue.poll()); key.cancel(); return; } if (readable) handleOperation(readQueue.poll()); if (writable) handleOperation(writeQueue.poll()); key.interestOps(getInterestOps()); if (getInterestOps() == 0) key.cancel(); //XXX is this ok? } }