/* $Id$ */ package ibis.ipl.impl.multi; import ibis.ipl.ConnectionFailedException; import ibis.ipl.ConnectionsFailedException; import ibis.ipl.Ibis; import ibis.ipl.IbisIdentifier; import ibis.ipl.NoSuchPropertyException; import ibis.ipl.PortType; import ibis.ipl.ReceivePortIdentifier; import ibis.ipl.SendPort; import ibis.ipl.SendPortDisconnectUpcall; import ibis.ipl.SendPortIdentifier; import ibis.ipl.WriteMessage; import ibis.util.ThreadPool; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MultiSendPort implements SendPort { /** Debugging output. */ private static final Logger logger = LoggerFactory.getLogger(MultiSendPort.class); private final ManageableMapper ManageableMapper; private final HashMap<String, SendPort>subPortMap = new HashMap<String, SendPort>(); private ArrayList<DowncallHandler> handlers = new ArrayList<DowncallHandler>(); private final PortType portType; private final MultiSendPortIdentifier id; private final String name; private final MultiIbis ibis; private SendPort activeSendPort; private String activeIbisName; private final ArrayList<ReceivePortIdentifier>idQueue = new ArrayList<ReceivePortIdentifier>(); private final ArrayList<IOException>errorQueue = new ArrayList<IOException>(); private int handlerCount; private final class DowncallHandler implements Runnable { private static final int OPP_NOOP = -1; private static final int OPP_QUIT = 0; private static final int OPP_CONNECT_RPID_ARRAY = 1; private static final int OPP_CONNECT_IID_NAME_MAP = 2; private static final int OPP_CONNECT_RPID = 3; private static final int OPP_CONNECT_IID_NAME = 4; private final SendPort subPort; private final String ibisName; int opcode = OPP_NOOP; ReceivePortIdentifier[] rpids; Map<IbisIdentifier, String>iidMap; IbisIdentifier id; String name; ReceivePortIdentifier rpid; long timeout; boolean fillTimeout; public DowncallHandler(SendPort subPort, String ibisName) { this.subPort = subPort; this.ibisName = ibisName; } private boolean setActive() { boolean set = false; synchronized (idQueue) { if (activeSendPort == null) { if (logger.isDebugEnabled()) { logger.debug("Setting active SendPort port: " + subPort); } activeSendPort = subPort; activeIbisName = ibisName; idQueue.notifyAll(); set = true; } else { try { if (logger.isDebugEnabled()) { logger.debug("Closing Inactive SendPort port: " + subPort); } subPort.close(); } catch (IOException e) { // Ignored } } opcode = OPP_NOOP; } return set; } private boolean setActive(ReceivePortIdentifier portId) { synchronized (idQueue) { boolean ret = setActive(); if (ret) { idQueue.add(portId); } return ret; } } private void setError(IOException e) { synchronized (idQueue) { if (activeSendPort == null) { if (logger.isDebugEnabled()) { logger.debug("Got Error While Connecting SendPort: " + subPort + " : " + e.getMessage()); } errorQueue.add(e); handlerCount++; if (handlerCount >= handlers.size()) { if (logger.isDebugEnabled()) { logger.debug("Notifying due to all handlers being done."); } idQueue.notifyAll(); } } opcode = OPP_NOOP; } } public void run() { while (opcode != OPP_QUIT) { if (logger.isDebugEnabled()) { logger.debug("Running:" + opcode + " for: " + subPort); } switch (opcode) { case OPP_NOOP: synchronized (idQueue) { // Check again to make sure it didn't change while we locked. // This makes sure we hold the idQueue lock as little as possible if (opcode == OPP_NOOP) { try { logger.debug("Handler waiting."); idQueue.wait(); } catch (InterruptedException e) { // Ignored } } if (activeSendPort != null) { logger.debug("We got beat to connect!"); opcode = OPP_NOOP; } } break; case OPP_QUIT: break; case OPP_CONNECT_RPID_ARRAY: try { for (int i=0; i<rpids.length; i++) { rpids[i] = ((MultiReceivePortIdentifier)rpids[i]).getSubId(ibisName); } subPort.connect(rpids, timeout, fillTimeout); setActive(); } catch (ConnectionsFailedException e) { setError(e); } break; case OPP_CONNECT_IID_NAME_MAP: try { HashMap<IbisIdentifier, String>ids = new HashMap<IbisIdentifier, String>(); for (IbisIdentifier id:iidMap.keySet()) { MultiIbisIdentifier mid = (MultiIbisIdentifier)id; ids.put(mid.subIdForIbis(ibisName), iidMap.get(id)); } ReceivePortIdentifier[] portId = subPort.connect(ids, timeout, fillTimeout); // TODO: Should add all of them. idQueue.add(portId[0]); setActive(); } catch (ConnectionsFailedException e) { errorQueue.add(e); } break; case OPP_CONNECT_RPID: try { rpid = ((MultiReceivePortIdentifier)rpid).getSubId(ibisName); subPort.connect(rpid, timeout, fillTimeout); setActive(); } catch (ConnectionFailedException e) { errorQueue.add(e); } break; case OPP_CONNECT_IID_NAME: try { MultiIbisIdentifier mid = (MultiIbisIdentifier)id; ReceivePortIdentifier portId = subPort.connect(mid.subIdForIbis(ibisName), name, timeout, fillTimeout); setActive(portId); } catch (ConnectionFailedException e) { errorQueue.add(e); } break; } } } } private final class DisconnectUpcaller implements SendPortDisconnectUpcall { MultiSendPort port; SendPortDisconnectUpcall upcaller; String ibisName; public DisconnectUpcaller(String ibisName, MultiSendPort port, SendPortDisconnectUpcall upcaller) { this.port = port; this.upcaller = upcaller; this.ibisName = ibisName; } public void lostConnection(SendPort me, ReceivePortIdentifier johnDoe, Throwable reason) { if (logger.isDebugEnabled()) { logger.debug("Passing lost connection along: " + me + " : " + johnDoe + " : " + reason.getMessage()); } try { upcaller.lostConnection(ibis.sendPortMap.get(me), ibis.mapReceivePortIdentifier(johnDoe, ibisName), reason); } catch (IOException e) { // TODO What the hell to do here? } } } @SuppressWarnings({ "unchecked", "rawtypes" }) public MultiSendPort(PortType type, MultiIbis ibis, String name, SendPortDisconnectUpcall connectUpcall, Properties props) throws IOException { if (logger.isDebugEnabled()) { logger.debug("Constructing MultiSendPort"); } for (String ibisName:ibis.subIbisMap.keySet()) { Ibis subIbis = ibis.subIbisMap.get(ibisName); DisconnectUpcaller upcaller = null; if (connectUpcall != null) { upcaller = new DisconnectUpcaller(ibisName, this, connectUpcall); } SendPort subPort = subIbis.createSendPort(type, name, upcaller, props); DowncallHandler handler = new DowncallHandler(subPort, ibisName); handlers .add(handler); subPortMap.put(ibisName, subPort); ibis.sendPortMap.put(subPort, this); ThreadPool.createNew(handler, "Connect Handler: " + ibisName); } ManageableMapper = new ManageableMapper((Map)subPortMap); this.portType = type; this.id = new MultiSendPortIdentifier(ibis.identifier(), name); this.name = name; this.ibis = ibis; } public void close() throws IOException { if (logger.isDebugEnabled()) { logger.debug("Closing port: " + this); } for (SendPort port:subPortMap.values()) { try { port.close(); } catch (IOException e) { // TODO Bundle up exceptions } } ibis.closeSendPort(this); } public void connect(ReceivePortIdentifier receiver) throws ConnectionFailedException { connect(receiver, 0L, true); } public synchronized void connect(ReceivePortIdentifier receiver, long timeoutMillis, boolean fillTimeout) throws ConnectionFailedException { synchronized (idQueue) { logger.debug("Attempting to connect."); errorQueue.clear(); idQueue.clear(); handlerCount = 0; if (activeSendPort == null) { logger.debug("No active sendport."); for (DowncallHandler handler:handlers) { handler.timeout = timeoutMillis; handler.fillTimeout = fillTimeout; handler.opcode = DowncallHandler.OPP_CONNECT_RPID; handler.rpid = receiver; } idQueue.notifyAll(); while (activeSendPort == null && handlerCount < handlers.size()) { try { if (logger.isDebugEnabled()) { logger.debug("Waiting for connection to open."); } idQueue.wait(); } catch (InterruptedException e) { // Ignored } } if (logger.isDebugEnabled()) { logger.debug("Done waiting for connection."); } if (activeSendPort == null) { if (logger.isDebugEnabled()) { logger.debug("No Connection. Throwing exception."); } // TODO: Map exception properly if (errorQueue.size() == 0) { throw new ConnectionFailedException("Unable to open connection.", receiver); } else { throw new ConnectionFailedException("Unable to open connection.", receiver, errorQueue.get(0)); } } } else { if (logger.isDebugEnabled()) { logger.debug("Using active send port to connect"); } activeSendPort.connect(receiver, timeoutMillis, fillTimeout); } } } public ReceivePortIdentifier connect(IbisIdentifier id, String name) throws ConnectionFailedException { return connect(id, name, 0L, true); } public synchronized ReceivePortIdentifier connect(IbisIdentifier id, String name, long timeoutMillis, boolean fillTimeout) throws ConnectionFailedException { synchronized (idQueue) { if (id == null) { throw new IllegalArgumentException("Null ibis identifier!"); } if (logger.isDebugEnabled()) { logger.debug("Connecting to: " + id + ":" + name + " timeout: " + timeoutMillis + " fill: " + fillTimeout); } errorQueue.clear(); idQueue.clear(); handlerCount = 0; if (activeSendPort == null) { if (logger.isDebugEnabled()) { logger.debug("No active connection..."); } for (DowncallHandler handler:handlers) { handler.timeout = timeoutMillis; handler.fillTimeout = fillTimeout; handler.opcode = DowncallHandler.OPP_CONNECT_IID_NAME; handler.id = id; handler.name = name; } // Wake all the handlers up. idQueue.notifyAll(); while (activeSendPort == null && handlerCount < handlers.size()) { try { logger.debug("Waiting for handler to connect."); idQueue.wait(); } catch (InterruptedException e) { // Ignored } } if (activeSendPort == null) { // TODO Map exceptions properly if (errorQueue.size() == 0) { throw new ConnectionFailedException("Unable to open connection.", id, name); } else { throw new ConnectionFailedException("Unable to open connection.", id, name, errorQueue.get(0)); } } else { try { return ibis.mapReceivePortIdentifier(idQueue.get(0), activeIbisName); } catch (IOException e) { // TODO Howto fill in right identifier? throw new ConnectionFailedException("Unable to map identifier.", null, idQueue.get(0).name(), e); } } } else { logger.debug("Using active send port to connect."); // TODO catch and map exception try { MultiIbisIdentifier ident = (MultiIbisIdentifier)id; return ibis.mapReceivePortIdentifier(activeSendPort.connect(ident.subIdForIbis(activeIbisName), name, timeoutMillis, fillTimeout), activeIbisName); } catch (IOException e) { throw new ConnectionFailedException("Unable to map identifier.", null, name, e); } } } } public void connect(ReceivePortIdentifier[] ports) throws ConnectionsFailedException { connect(ports, 0L, true); } public void connect(ReceivePortIdentifier[] ports, long timeoutMillis, boolean fillTimeout) throws ConnectionsFailedException { synchronized (idQueue) { logger.debug("Connecting..."); errorQueue.clear(); idQueue.clear(); handlerCount = 0; if (activeSendPort == null) { logger.debug("No active connection"); for (DowncallHandler handler:handlers) { handler.timeout = timeoutMillis; handler.fillTimeout = fillTimeout; handler.opcode = DowncallHandler.OPP_CONNECT_RPID_ARRAY; handler.rpids = ports; } idQueue.notifyAll(); while (activeSendPort == null && handlerCount < handlers.size()) { try { logger.debug("Waiting for handler to connect..."); idQueue.wait(); } catch (InterruptedException e) { // Ignored } } if (activeSendPort == null) { // TODO Handle Connections Failed properly. throw new ConnectionsFailedException("All of them!"); } } else { logger.debug("Using active send port to connect"); // TODO Catch exception and remap activeSendPort.connect(ports, timeoutMillis, fillTimeout); } } } public ReceivePortIdentifier[] connect(Map<IbisIdentifier, String> ports) throws ConnectionsFailedException { return connect(ports, 0L, true); } public ReceivePortIdentifier[] connect(Map<IbisIdentifier, String> ports, long timeoutMillis, boolean fillTimeout) throws ConnectionsFailedException { synchronized (idQueue) { logger.debug("Connecting..."); errorQueue.clear(); idQueue.clear(); handlerCount = 0; if (activeSendPort == null) { logger.debug("No active sendport"); for (DowncallHandler handler:handlers) { handler.timeout = timeoutMillis; handler.fillTimeout = fillTimeout; handler.opcode = DowncallHandler.OPP_CONNECT_IID_NAME_MAP; handler.iidMap = ports; } idQueue.notifyAll(); while (activeSendPort == null && handlerCount < handlers.size()) { try { logger.debug("Waiting for connection..."); idQueue.wait(); } catch (InterruptedException e) { // Ignored } } if (activeSendPort == null) { // TODO Handle Connections Failed properly. throw new ConnectionsFailedException("All of them!"); } else { ReceivePortIdentifier[] ids = activeSendPort.connectedTo(); for (int i=0; i<ids.length; i++) { try { ids[i] = ibis.mapReceivePortIdentifier(ids[i], activeIbisName); } catch (IOException e) { // TODO Should we ignore this? } } return ids; } } else { logger.debug("Using active send port to connect."); // TODO Catch and map exception ReceivePortIdentifier[] ids = activeSendPort.connect(ports, timeoutMillis, fillTimeout); for (int i=0; i<ids.length; i++) { try { ids[i] = ibis.mapReceivePortIdentifier(ids[i], activeIbisName); } catch (IOException e) { // TODO Should we ignore this? } } return ids; } } } public ReceivePortIdentifier[] connectedTo() { HashMap<ReceivePortIdentifier, String>idList = new HashMap<ReceivePortIdentifier, String>(); for(String ibisName:subPortMap.keySet()) { SendPort subPort = subPortMap.get(ibisName); ReceivePortIdentifier[] ids = subPort.connectedTo(); for (ReceivePortIdentifier id:ids) { try { idList.put(ibis.mapReceivePortIdentifier(id, ibisName), ibisName); } catch (IOException e) { // TODO Should we ignore this? } } } return idList.keySet().toArray(new ReceivePortIdentifier[idList.size()]); } public void disconnect(ReceivePortIdentifier receiver) throws IOException { MultiIbisIdentifier id = (MultiIbisIdentifier)receiver.ibisIdentifier(); for(String ibisName:subPortMap.keySet()) { SendPort subPort = subPortMap.get(ibisName); IbisIdentifier subId = id.subIdForIbis(ibisName); try { subPort.disconnect(subId, receiver.name()); } catch (IOException e) { // TODO: Bundle IO Exceptions } } } public void disconnect(IbisIdentifier identifier, String name) throws IOException { MultiIbisIdentifier id = (MultiIbisIdentifier)identifier; for(String ibisName:subPortMap.keySet()) { SendPort subPort = subPortMap.get(ibisName); IbisIdentifier subId = id.subIdForIbis(ibisName); try { subPort.disconnect(subId, name); } catch (IOException e) { // TODO: Bundle IO Exceptions } } } public PortType getPortType() { return portType; } public SendPortIdentifier identifier() { return id; } public ReceivePortIdentifier[] lostConnections() { HashMap<ReceivePortIdentifier, String>idList = new HashMap<ReceivePortIdentifier, String>(); for(String ibisName:subPortMap.keySet()) { SendPort subPort = subPortMap.get(ibisName); ReceivePortIdentifier[] ids = subPort.lostConnections(); for (int i=0; i< ids.length; i++) { try { idList.put(ibis.mapReceivePortIdentifier(ids[i], ibisName), ibisName); } catch (IOException e) { // TODO Should we ignore this? } } } return idList.keySet().toArray(new ReceivePortIdentifier[idList.size()]); } public String name() { return name; } public WriteMessage newMessage() throws IOException { // TODO: Throw error if activeSendPort is null? return new MultiWriteMessage(activeSendPort.newMessage(), this); } public String getManagementProperty(String key) throws NoSuchPropertyException { return ManageableMapper.getManagementProperty(key); } public Map<String, String> managementProperties() { return ManageableMapper.managementProperties(); } public void printManagementProperties(PrintStream stream) { ManageableMapper.printManagementProperties(stream); } public void setManagementProperties(Map<String, String> properties) throws NoSuchPropertyException { ManageableMapper.setManagementProperties(properties); } public void setManagementProperty(String key, String value) throws NoSuchPropertyException { ManageableMapper.setManagementProperty(key, value); } public void quit(MultiSendPort port) { for(DowncallHandler handler:port.handlers) { handler.opcode = DowncallHandler.OPP_QUIT; synchronized (handler) { handler.notifyAll(); } } } }