/* $Id$ */ package ibis.ipl.impl.nio; import ibis.ipl.ConnectionClosedException; import ibis.ipl.MessageUpcall; import ibis.ipl.PortType; import ibis.ipl.ReceivePortConnectUpcall; import ibis.ipl.ReceiveTimedOutException; import ibis.ipl.impl.Ibis; import ibis.ipl.impl.ReadMessage; import ibis.ipl.impl.ReceivePortConnectionInfo; import ibis.ipl.impl.SendPortIdentifier; import ibis.util.ThreadPool; import java.io.IOException; import java.nio.channels.Channel; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; abstract class NioReceivePort extends ibis.ipl.impl.ReceivePort implements Runnable, Protocol { private static Logger logger = LoggerFactory.getLogger(NioReceivePort.class); private boolean reader_busy = false; static class ConnectionInfo extends ReceivePortConnectionInfo { ConnectionInfo(SendPortIdentifier origin, NioReceivePort port, NioDissipator dissipator) throws IOException { super(origin, port, dissipator); dissipator.info = this; } protected void upcallCalledFinish() { super.upcallCalledFinish(); ThreadPool.createNew((NioReceivePort) port, "NioReceivePort with upcall"); } } NioReceivePort(Ibis ibis, PortType type, String name, MessageUpcall upcall, ReceivePortConnectUpcall connUpcall, Properties properties) throws IOException { super(ibis, type, name, upcall, connUpcall, properties); if (upcall != null) { ThreadPool.createNew(this, "NioReceivePort with upcall"); } } void addConnection(SendPortIdentifier id, NioDissipator dissipator) throws IOException { ConnectionInfo info = new ConnectionInfo(id, this, dissipator); addInfo(id, info); } /** * Sees if the user is ok with a new connection from "spi" Called by the * connection factory. * * @return the reply for the send port */ byte connectionRequested(SendPortIdentifier spi, PortType capabilities, Channel channel) { if (logger.isDebugEnabled()) { logger.debug("handling connection request"); } byte r = connectionAllowed(spi, capabilities); if (r != ACCEPTED) { return r; } try { newConnection(spi, channel); } catch (IOException e) { lostConnection(spi, e); logger.error("newConnection() failed"); return DENIED; } if (logger.isInfoEnabled()) { logger.info("new incoming connection from " + spi + " to " + ident); } return r; } /** * Waits for someone to wake us up. Waits: - not at all if deadline == -1 - * until System.getTimeMillis >= deadline if deadline > 0 - for(ever) if * deadline == 0 * * This method assumes that the caller holds the monitor on this instance. * * @return true we (might have been) notified, or false if the deadline * passed */ private boolean waitForNotify(long deadline) { if (deadline == 0) { try { wait(); } catch (InterruptedException e) { // IGNORE } return true; } else if (deadline == -1) { return false; // deadline always passed } long time = System.currentTimeMillis(); if (time >= deadline) { return false; } try { wait(deadline - time); } catch (InterruptedException e) { // IGNORE } return true; // don't know if we have been notified, but could be... } /** * gets a new message from the network. Will block until the deadline has * passed, or not at all if deadline = -1, or indefinitely if deadline = 0. * Only used when upcalls are disabled. Uses global message "m" to ensure * only one message is alive at any time * */ public ReadMessage getMessage(long timeout) throws IOException { NioDissipator dissipator; long deadline = timeout; if (deadline > 0) { deadline += System.currentTimeMillis(); } if (logger.isDebugEnabled()) { logger.debug("trying to fetch message"); } synchronized(this) { while (reader_busy && ! closed) { if (!waitForNotify(deadline)) { logger.error("timeout while waiting on previous message"); throw new ReceiveTimedOutException("previous message" + " not finished yet"); } } // Wait until there is a connection while (connections.size() == 0 && ! closed) { if (!waitForNotify(deadline)) { logger.error("timeout while waiting for connection"); throw new ReceiveTimedOutException("no connection yet"); } } // Wait until the current message is done while (message != null && ! closed) { if (!waitForNotify(deadline)) { logger.error( "timeout while waiting on previous message"); throw new ReceiveTimedOutException("previous message" + " not finished yet"); } } if (closed) { throw new IOException("receive() on closed port"); } reader_busy = true; } try { dissipator = getReadyDissipator(deadline); ReceivePortConnectionInfo info = dissipator.info; info.message.setFinished(false); if (numbered) { try { info.message.setSequenceNumber(info.message.readLong()); } catch (IOException e) { errorOnRead(dissipator, e); // do recursive call reader_busy = false; return getMessage(deadline); } } messageArrived(info.message); if (logger.isDebugEnabled()) { logger.debug("new message received"); } return message; } catch (ReceiveTimedOutException e) { logger.debug("timeout while waiting on dissipator with message"); throw e; } catch (ConnectionClosedException e) { logger.debug("receiveport closed while waiting on message"); throw e; } finally { synchronized (this) { reader_busy = false; notifyAll(); } } } protected ReadMessage doPoll() throws IOException { try { return getMessage(-1); } catch (ReceiveTimedOutException e) { // IGNORE } return null; } public void closePort(long timeout) { closing(); // signal the subclass we are closing down if (upcall != null) { super.closePort(timeout); } else { try { getMessage(timeout); } catch(ConnectionClosedException e) { // OK } catch(IOException e2) { super.closePort(1); } } } public void run() { NioDissipator dissipator; Thread.currentThread().setName(this + " upcall thread"); while (true) { try { dissipator = getReadyDissipator(0); } catch (ConnectionClosedException e2) { synchronized (this) { // the receiveport was closed, exit notifyAll(); return; } } catch (IOException e) { // FIXME: this is not very nice continue; } ReceivePortConnectionInfo info = dissipator.info; info.message.setFinished(false); if (numbered) { try { info.message.setSequenceNumber(info.message.readLong()); } catch (IOException e) { errorOnRead(dissipator, e); continue; } } ReadMessage m = info.message; messageArrived(info.message); if (m.finishCalledInUpcall()) { // a new thread was started to handle the next message, // exit return; } } } /** * A new connection has been established. */ abstract void newConnection(SendPortIdentifier spi, Channel channel) throws IOException; abstract void errorOnRead(NioDissipator dissipator, Exception cause); /** * Searches for a dissipator with a message waiting * * Will block until the deadline has passed, or not at all if deadline = -1, * or indefinitely if deadline = 0 * * @param deadline * the deadline after which searching has failed * * @throws ReceiveTimedOutException * If no connections are ready after the deadline has passed * * @throws ConnectionClosedException * if there a no more connections left and the receiveport is * closing down. */ abstract NioDissipator getReadyDissipator(long deadline) throws IOException; /** * this receiveport is closing down. */ abstract void closing(); }