/* $Id$ */ package ibis.ipl.impl; import ibis.io.SerializationInput; import ibis.ipl.ConnectionClosedException; import ibis.ipl.IbisConfigurationException; import ibis.ipl.MessageUpcall; import ibis.ipl.PortType; import ibis.ipl.ReceivePortConnectUpcall; import ibis.ipl.ReceiveTimedOutException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of the {@link ibis.ipl.ReceivePort} interface, to be extended * by specific Ibis implementations. */ public abstract class ReceivePort extends Manageable implements ibis.ipl.ReceivePort { /** Debugging output. */ private static final Logger logger = LoggerFactory.getLogger("ibis.ipl.impl.ReceivePort"); // Possible results of a connection attempt. /** Connection attempt accepted. */ public static final byte ACCEPTED = 0; /** Connection attempt denied by user code. */ public static final byte DENIED = 1; /** Connection attempt disabled. */ public static final byte DISABLED = 2; /** Sendport was already connected to this receiveport. */ public static final byte ALREADY_CONNECTED = 3; /** Port type does not match. */ public static final byte TYPE_MISMATCH = 4; /** Receiveport not found. */ public static final byte NOT_PRESENT = 5; /** Receiveport already has a connection, and ManyToOne is not specified. */ public static final byte NO_MANY_TO_X = 6; final static Set<Thread> threadsInUpcallSet = Collections.synchronizedSet(new HashSet<Thread>()); /** The type of this port. */ public final PortType type; /** The name of this port. */ public final String name; /** Set when connections are enabled. */ private boolean connectionsEnabled = false; /** Set when connection downcalls are supported. */ private boolean connectionDowncalls = false; /** The identification of this receiveport. */ public final ReceivePortIdentifier ident; /** * The connections lost since the last call to {@link #lostConnections()}. */ private ArrayList<SendPortIdentifier> lostConnections = new ArrayList<SendPortIdentifier>(); /** * The new connections since the last call to {@link #newConnections()}. */ private ArrayList<SendPortIdentifier> newConnections = new ArrayList<SendPortIdentifier>(); /** Message upcall, if specified, or <code>null</code>. */ protected MessageUpcall upcall; /** Connection upcall handler, or <code>null</code>. */ protected ReceivePortConnectUpcall connectUpcall; /** The current connections. */ protected HashMap<SendPortIdentifier, ReceivePortConnectionInfo> connections = new HashMap<SendPortIdentifier, ReceivePortConnectionInfo>(); /** Set when upcalls are enabled. */ protected boolean allowUpcalls = false; /** The Ibis instance of this receive port. */ protected Ibis ibis; /** Set when messages are numbered. */ protected final boolean numbered; /** The serialization for this receive port. */ protected final String serialization; /** Set when this port is closed. */ protected boolean closed = false; /** The current message. */ protected ReadMessage message = null; /** * Set when the current message has been delivered. Only used for * explicit receive. */ protected boolean delivered = false; /** Properties. */ protected final Properties properties; private long nMessages = 0; private long messageBytes = 0; private long bytes = 0; private long nConnections = 0; private long nLostConnections = 0; private long nClosedConnections = 0; private int outstanding; // For connections that have been allowed but are not // actually present yet. /** * Constructs a <code>ReceivePort</code> with the specified parameters. * Note that all property checks are already performed in the * <code>Ibis.createReceivePort</code> methods. * @param ibis the ibis instance. * @param type the port type. * @param name the name of the <code>ReceivePort</code>. * @param upcall the message upcall object, or <code>null</code>. * @param connectUpcall the connection upcall object, or <code>null</code>. * @param properties the port properties. */ @SuppressWarnings("unchecked") protected ReceivePort(Ibis ibis, PortType type, String name, MessageUpcall upcall, ReceivePortConnectUpcall connectUpcall, Properties properties) throws IOException { this.ibis = ibis; this.type = type; this.name = name; this.ident = ibis.createReceivePortIdentifier(name, ibis.ident); this.upcall = upcall; this.connectUpcall = connectUpcall; this.connectionDowncalls = type.hasCapability(PortType.CONNECTION_DOWNCALLS); this.numbered = type.hasCapability(PortType.COMMUNICATION_NUMBERED); this.properties = ibis.properties(); if (properties != null) { for (Enumeration<String> e = (Enumeration<String>)properties.propertyNames(); e.hasMoreElements();) { String key = e.nextElement(); String value = properties.getProperty(key); this.properties.setProperty(key, value); } } if (type.hasCapability(PortType.SERIALIZATION_DATA)) { serialization = "data"; } else if (type.hasCapability(PortType.SERIALIZATION_OBJECT_SUN)) { serialization = "sun"; } else if (type.hasCapability(PortType.SERIALIZATION_OBJECT_IBIS)) { serialization = "ibis"; } else if (type.hasCapability(PortType.SERIALIZATION_OBJECT)) { serialization = "object"; } else { serialization = "byte"; } ibis.register(this); if (logger.isDebugEnabled()) { logger.debug(ibis.ident + ": ReceivePort '" + name + "' created"); } addValidKey("Messages"); addValidKey("MessageBytes"); addValidKey("Bytes"); addValidKey("Connections"); addValidKey("LostConnections"); addValidKey("ClosedConnections"); } protected ReadMessage createReadMessage(SerializationInput in, ReceivePortConnectionInfo info) { return new ReadMessage(in, info); } public synchronized void enableMessageUpcalls() { allowUpcalls = true; notifyAll(); } public synchronized Map<IbisIdentifier, Set<String>> getConnectionTypes() { HashMap<IbisIdentifier, Set<String>> result = new HashMap<IbisIdentifier, Set<String>>(); for (SendPortIdentifier port : connections.keySet()) { ReceivePortConnectionInfo i = connections.get(port); if (i != null) { IbisIdentifier id = port.ibis; Set<String> s = result.get(id); if (s == null) { s = new HashSet<String>(); } s.add(i.connectionType()); result.put(id, s); } } return result; } public static String getString(int result) { switch(result) { case ACCEPTED: return "ACCEPTED"; case DENIED: return "DENIED"; case NO_MANY_TO_X: return "NO_MANYTOONE"; case DISABLED: return "DISABLED"; case ALREADY_CONNECTED: return "ALREADY_CONNECTED"; case TYPE_MISMATCH: return "TYPE_MISMATCH"; case NOT_PRESENT: return "NOT_PRESENT"; } return "UNKNOWN"; } public synchronized void disableMessageUpcalls() { allowUpcalls = false; } public synchronized ibis.ipl.SendPortIdentifier[] connectedTo() { return connections.keySet().toArray(new ibis.ipl.SendPortIdentifier[0]); } public PortType getPortType() { return type; } public synchronized ibis.ipl.SendPortIdentifier[] lostConnections() { if (! connectionDowncalls) { throw new IbisConfigurationException("ReceivePort.lostConnections()" + " called but connectiondowncalls not configured"); } ibis.ipl.SendPortIdentifier[] result = lostConnections.toArray( new ibis.ipl.SendPortIdentifier[0]); lostConnections.clear(); return result; } public synchronized ibis.ipl.SendPortIdentifier[] newConnections() { if (! connectionDowncalls) { throw new IbisConfigurationException("ReceivePort.newConnections()" + " called but connectiondowncalls not configured"); } ibis.ipl.SendPortIdentifier[] result = newConnections.toArray( new ibis.ipl.SendPortIdentifier[0]); newConnections.clear(); return result; } public String name() { return name; } public ibis.ipl.ReceivePortIdentifier identifier() { return ident; } public synchronized void enableConnections() { connectionsEnabled = true; } public synchronized void disableConnections() { connectionsEnabled = false; } public ReadMessage receive() throws IOException { return receive(0); } public ReadMessage receive(long timeout) throws IOException { if (upcall != null) { throw new IbisConfigurationException( "Configured Receiveport for upcalls, downcall not allowed"); } if (timeout < 0) { throw new IOException("timeout must be a non-negative number"); } if (timeout > 0 && ! type.hasCapability(PortType.RECEIVE_TIMEOUT)) { throw new IbisConfigurationException( "This port is not configured for receive() with timeout"); } return getMessage(timeout); } public final void close() throws IOException { close(0L); } public void close(long timeout) throws IOException { if (logger.isDebugEnabled()) { logger.debug("Receiveport " + name + ": closing"); } synchronized(this) { if (closed) { throw new IOException("Port already closed"); } connectionsEnabled = false; closed = true; notifyAll(); } closePort(timeout); ibis.deRegister(this); } public synchronized byte connectionAllowed(SendPortIdentifier id, PortType sp) { byte retval = ACCEPTED; if (isConnectedTo(id)) { retval = ALREADY_CONNECTED; } else if (! sp.equals(type)) { retval = TYPE_MISMATCH; } else if (! connectionsEnabled) { retval = DISABLED; } else if ((outstanding != 0 || connections.size() != 0) && ! (type.hasCapability(PortType.CONNECTION_MANY_TO_ONE) || type.hasCapability(PortType.CONNECTION_MANY_TO_MANY))) { retval = NO_MANY_TO_X; } else if (connectUpcall != null) { retval = DENIED; try { if (connectUpcall.gotConnection(this, id)) { retval = ACCEPTED; } } catch(Throwable e) { logger.error("Unexpected exception in gotConnection(), " + "this Java instance will be terminated" , e); System.exit(1); } } if (retval == ACCEPTED && connectionDowncalls) { newConnections.add(id); } if (logger.isDebugEnabled()) { logger.debug("Connection attempt from " + id + ": " + getString(retval)); } if (retval == ACCEPTED) { outstanding++; } return retval; } public int hashCode() { return name.hashCode(); } public boolean equals(Object obj) { if (obj instanceof ReceivePort) { ReceivePort other = (ReceivePort) obj; return name.equals(other.name); } else if (obj instanceof String) { String s = (String) obj; return s.equals(name); } else { return false; } } public synchronized boolean isConnectedTo(SendPortIdentifier id) { return getInfo(id) != null; } public ReadMessage poll() throws IOException { if (! type.hasCapability(PortType.RECEIVE_POLL)) { throw new IbisConfigurationException( "Receiveport not configured for polls"); } return doPoll(); } // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Public methods, may be called or redefined by implementations. // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ /** * Notifies this receiveport that the connection associated with the * specified sendport must be assumed to be lost, due to the specified * reason. It updates the administration, and performs the * lostConnection upcall, if required. * @param id the identification of the sendport. * @param e the cause of the lost connection. */ public void lostConnection(SendPortIdentifier id, Throwable e) { if (connectionDowncalls) { synchronized(this) { lostConnections.add(id); } } if (connectUpcall != null) { try { connectUpcall.lostConnection(this, id, e); } catch(Throwable e2) { logger.error("Unexpected exception in lostConnection(), " + "this Java instance will be terminated" , e2); System.exit(1); } } if (e != null) { nLostConnections++; } else { nClosedConnections++; } removeInfo(id); } /** * Returns the connection information for the specified sendport identifier. * @param id the identification of the sendport. * @return the connection information, or <code>null</code> if not * present. */ public synchronized ReceivePortConnectionInfo getInfo( SendPortIdentifier id) { return connections.get(id); } /** * Adds a connection entry for the specified sendport identifier, * and notifies, for possible waiters on a new connection. * @param id the identification of the sendport. * @param info the associated connection information. */ public synchronized void addInfo(SendPortIdentifier id, ReceivePortConnectionInfo info) { nConnections++; connections.put(id, info); outstanding--; notifyAll(); } /** * Removes the connection entry for the specified sendport identifier. * If after this there are no connections left, a notify is done. * A {@link #closePort} can wait for this to happen. * @param id the identification of the sendport. * @return the removed connection. */ public synchronized ReceivePortConnectionInfo removeInfo( SendPortIdentifier id) { ReceivePortConnectionInfo info = connections.remove(id); if (connections.size() == 0) { notifyAll(); } return info; } /** * Returns an array with entries for each connection. * @return the connections. */ public synchronized ReceivePortConnectionInfo[] connections() { return connections.values().toArray(new ReceivePortConnectionInfo[0]); } /** * Waits for a new message and returns it. If the specified timeout is * larger than 0, the implementation waits for the specified time. * This method gets called by {@link #receive()} or {@link #receive(long)}, * and may be redefined by implementations, for instance when there * is no separate thread delivering the messages. * @param timeout the timeout in milliseconds. * @exception ReceiveTimedOutException is thrown on timeout. * @exception IOException is thrown in case of trouble. * @return the new message, or <code>null</code>. */ public ReadMessage getMessage(long timeout) throws IOException { long deadLine = System.currentTimeMillis() + timeout; synchronized(this) { // Wait until a new message is delivered or the port is closed. while ((message == null || delivered) && ! closed) { try { if (timeout > 0) { long time = System.currentTimeMillis(); if (time >= deadLine) { throw new ReceiveTimedOutException( "timeout expired in receive()"); } time = deadLine - time; wait(time); } else { wait(); } } catch(InterruptedException e) { // ignored } if (closed) { throw new ConnectionClosedException("receive() on closed port"); } } delivered = true; return message; } } /** * This method is called when a message arrived and the port is configured * for upcalls. * The assumption here is that when the upcall does an explicit * {@link ReadMessage#finish()}, a new message is allocated, because * at that point new messages can be delivered to the receive port. * This method may be redefined, for instance when there is a separate * thread for dealing with upcalls. * @param msg the message. */ public void doUpcall(ReadMessage msg) { synchronized(this) { // Wait until upcalls are enabled. while (! allowUpcalls) { try { wait(); } catch(InterruptedException e) { // ignored } } } msg.setInUpcall(true); try { // Notify the message that is is processed from an upcall, // so that finish() calls can be detected. threadsInUpcallSet.add(Thread.currentThread()); upcall.upcall(msg); } catch(IOException e) { if (! msg.isFinished()) { msg.finish(e); return; } logger.error("Got unexpected exception in upcall, continuing ...", e); } catch(ClassNotFoundException e) { if (logger.isDebugEnabled()) { logger.debug("Got exception from upcall", e); } if (! msg.isFinished()) { IOException ioex = new IOException("Got ClassNotFoundException: " + e.getMessage()); ioex.initCause(e); msg.finish(ioex); } return; } catch(Throwable e) { logger.error("Got unexpected throwable in upcall(), " + "this Java instance will be terminated", e); System.exit(1); } finally { msg.setInUpcall(false); } if (! msg.isFinished()) { try { msg.finish(); } catch(IOException e) { msg.finish(e); } } } public void messageArrived(ReadMessage msg) { // Wait until the previous message was finished. synchronized(this) { while (message != null) { try { wait(); } catch(InterruptedException e) { // ignored. } } message = msg; delivered = false; notifyAll(); } if (upcall != null) { doUpcall(message); } } /** * Notifies the port that {@link ReadMessage#finish()} was called on the * specified message. The port should prepare for a new message. * @param r the message. * @param cnt the byte count of this message. */ public void finishMessage(ReadMessage r, long cnt) { ibis.ipl.SendPortIdentifier[] ports; synchronized(this) { ports = connectedTo(); nMessages++; messageBytes += cnt; message = null; threadsInUpcallSet.remove(Thread.currentThread()); notifyAll(); } // This outside the lock, otherwise deadlock. ibis.addReceivedPerIbis(cnt, ports); } /** * Notifies the port that {@link ReadMessage#finish(IOException)} * was called on the specified message. * The port should close the connection, with the specified reason. * @param r the message. * @param e the Exception. */ public synchronized void finishMessage(ReadMessage r, IOException e) { r.getInfo().close(e); message = null; threadsInUpcallSet.remove(Thread.currentThread()); notifyAll(); } /** * Waits for all connections to close. If the specified timeout is larger * than 0, the implementation waits for the specified time, and then * forcibly closes all connections. * @param timeout the timeout in milliseconds. */ public synchronized void closePort(long timeout) { if (timeout == 0) { while (connections.size() > 0) { try { wait(); } catch(InterruptedException e) { // ignored } } } else { long endTime = System.currentTimeMillis() + timeout; while (connections.size() > 0 && timeout > 0) { try { wait(timeout); } catch(InterruptedException e) { // ignored } timeout = endTime - System.currentTimeMillis(); } ReceivePortConnectionInfo[] conns = connections(); for (int i = 0; i < conns.length; i++) { conns[i].close(new IOException( "receiver forcibly closed connection")); } nClosedConnections += conns.length; } if (logger.isDebugEnabled()) { logger.debug(name + ":done receiveport.close"); } } /** * Called in case an Ibis died or left. The connections originating from it must be * removed. * @param id the IbisIdentifier of the Ibis that left/died. */ protected synchronized void killConnectionsWith(ibis.ipl.IbisIdentifier id) { SendPortIdentifier[] keys = connections.keySet().toArray(new SendPortIdentifier[connections.size()]); for (SendPortIdentifier s : keys) { if (s.ibisIdentifier().equals(id)) { connections.get(s).close(new ConnectionClosedException("Connection origin died or left")); removeInfo(s); } } } protected synchronized void updateProperties() { setProperty("Bytes", "" + bytes); setProperty("ClosedConnections", "" + nClosedConnections); setProperty("Connections", "" + nConnections); setProperty("Messages", "" + nMessages); setProperty("MessageBytes", "" + messageBytes); setProperty("LostConnections", "" + nLostConnections); } protected void doProperties(Map<String, String> properties) { for (Map.Entry<String,String> entry : properties.entrySet()) { doProperty(entry.getKey(), entry.getValue()); } } protected void doProperty(String key, String value) { if (key.equals("Bytes")) { bytes = Long.parseLong(value); } else if (key.equals("ClosedConnections")) { nClosedConnections = Long.parseLong(value); } else if (key.equals("nConnections")) { nConnections = Long.parseLong(value); } else if (key.equals("Messages")) { nMessages = Long.parseLong(value); } else if (key.equals("MessageBytes")) { messageBytes = Long.parseLong(value); } else if (key.equals("LostConnections")) { nLostConnections = Long.parseLong(value); } } void addDataIn(long cnt) { bytes += cnt; } /** * Implementation-dependent part of the {@link #poll()} method. * This version assumes that other threads deliver messages and * do upcalls. * @exception IOException is thrown in case of trouble. * @return a new {@link ReadMessage} or <code>null</code>. */ protected ReadMessage doPoll() throws IOException { Thread.yield(); // Give other thread a chance to deliver if (upcall != null) { return null; } synchronized (this) { // Other thread may modify data. if (message == null || delivered) { return null; } if (message != null) { delivered = true; } return message; } } synchronized long getMessageCount() { return nMessages; } synchronized long getBytesReceived() { return bytes; } synchronized long getBytesRead() { return messageBytes; } }