/* $Id$ */ package ibis.ipl.impl.nio; import ibis.util.ThreadPool; 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class used as a single send/receive thread for an entire NioIbis instance. */ final class SendReceiveThread implements Runnable { static final int INITIAL_ARRAY_SIZE = 8; private static Logger logger = LoggerFactory.getLogger(SendReceiveThread.class); private ArrayList<SelectableChannel> pendingChannels = new ArrayList<SelectableChannel>(); private ArrayList<Object> pendingAttachments = new ArrayList<Object>(); private SelectionKey[] readyWriteKeys; private int nrOfReadyWriteKeys; private SelectionKey[] readyReadKeys; private int nrOfReadyReadKeys; private Selector selector; private boolean exit = false; SendReceiveThread() throws IOException { selector = Selector.open(); ThreadPool.createNew(this, "SendReceiveThread"); readyWriteKeys = new SelectionKey[INITIAL_ARRAY_SIZE]; readyReadKeys = new SelectionKey[INITIAL_ARRAY_SIZE]; } /** * Registers the given channel with our selector. * * @return The SelectionKey representing the registration, with the given * attachment attached to it. */ synchronized SelectionKey register(SelectableChannel channel, Object attachment) throws IOException { SelectionKey key = null; pendingChannels.add(channel); pendingAttachments.add(attachment); channel.configureBlocking(false); selector.wakeup(); while (key == null) { try { wait(); } catch (InterruptedException e) { // IGNORE } key = channel.keyFor(selector); } return key; } void registerPendingChannels() { SelectableChannel channel; SelectionKey key; if (pendingChannels.size() == 0) { return; } if (logger.isDebugEnabled()) { logger.debug("registering " + pendingChannels.size() + " channels"); } for (int i = 0; i < pendingChannels.size(); i++) { channel = pendingChannels.get(i); try { key = channel.register(selector, 0); key.attach(pendingAttachments.get(i)); } catch (IOException e) { // IGNORE } } pendingChannels.clear(); pendingAttachments.clear(); notifyAll(); } /** * signals a connection is ready for writing data */ synchronized void enableWriting(SelectionKey key) { if (logger.isDebugEnabled()) { logger.debug("queueing write enable"); } if (nrOfReadyWriteKeys == readyWriteKeys.length) { SelectionKey[] newKeys; newKeys = new SelectionKey[readyWriteKeys.length * 2]; for (int i = 0; i < readyWriteKeys.length; i++) { newKeys[i] = readyWriteKeys[i]; } readyWriteKeys = newKeys; } readyWriteKeys[nrOfReadyWriteKeys] = key; nrOfReadyWriteKeys++; selector.wakeup(); } /** * signals a connection is ready to read data */ synchronized void enableReading(SelectionKey key) { if (logger.isDebugEnabled()) { logger.debug("queueing read enable"); } if (nrOfReadyReadKeys == readyReadKeys.length) { SelectionKey[] newKeys; newKeys = new SelectionKey[readyReadKeys.length * 2]; for (int i = 0; i < readyReadKeys.length; i++) { newKeys[i] = readyReadKeys[i]; } readyReadKeys = newKeys; } readyReadKeys[nrOfReadyReadKeys] = key; nrOfReadyReadKeys++; selector.wakeup(); } void handlePendingKeys() { if (logger.isDebugEnabled()) { if (nrOfReadyWriteKeys != 0 || nrOfReadyReadKeys != 0) { logger .debug("enabling " + nrOfReadyWriteKeys + " write keys and " + nrOfReadyReadKeys + " read keys"); } } for (int i = 0; i < nrOfReadyWriteKeys; i++) { try { readyWriteKeys[i].interestOps(SelectionKey.OP_WRITE); } catch (CancelledKeyException e) { // Channels was closed/lost, ignore } readyWriteKeys[i] = null; } nrOfReadyWriteKeys = 0; for (int i = 0; i < nrOfReadyReadKeys; i++) { try { readyReadKeys[i].interestOps(SelectionKey.OP_READ); } catch (CancelledKeyException e) { // Channels was closed/lost, ignore } readyReadKeys[i] = null; } nrOfReadyReadKeys = 0; } private void send(SelectionKey key) { ThreadNioAccumulatorConnection out; out = (ThreadNioAccumulatorConnection) key.attachment(); out.threadSend(); } private void receive(SelectionKey key) { ThreadNioDissipator in; in = (ThreadNioDissipator) key.attachment(); in.doRead(); } /** * Stops the send/receive Thread */ synchronized void quit() { exit = true; } public void run() { Thread.currentThread().setName("send/receive thread"); // try to add some importance to this thread try { int max = Thread.currentThread().getThreadGroup().getMaxPriority(); int current = Thread.currentThread().getPriority(); Thread.currentThread().setPriority(Math.min(max, (current + 1))); } catch (Exception e) { // IGNORE } while (true) { if (logger.isDebugEnabled()) { logger.debug("looking for work"); } synchronized (this) { if (exit) { if (logger.isDebugEnabled()) { logger.debug("done looking for work"); } return; } registerPendingChannels(); handlePendingKeys(); } if (logger.isDebugEnabled()) { logger.debug("doing a select on " + selector.keys().size() + " channels"); } try { selector.select(); } catch (IOException e) { logger.warn("ibis.ipl.impl.nio.SendReceiveThread.run():" + " select failed with exception: " + e); // IGNORE } catch (CancelledKeyException e) { // INGORE } if (logger.isDebugEnabled()) { logger.debug("selected " + selector.selectedKeys().size() + " channel(s)"); } for (SelectionKey key : selector.selectedKeys()) { if (key.attachment() == null) { continue; // skip this key, nothing attached to it } try { if (key.isWritable()) { send(key); } if (key.isReadable()) { receive(key); } } catch (CancelledKeyException e) { // key was cancelled or channel was closed // skip this key } } selector.selectedKeys().clear(); if (logger.isDebugEnabled()) { logger.debug("done"); } } } }