/* $Id$ */ package ibis.ipl.impl.smartsockets; import ibis.io.BufferedArrayInputStream; import ibis.io.BufferedArrayOutputStream; import ibis.ipl.AlreadyConnectedException; import ibis.ipl.CapabilitySet; import ibis.ipl.ConnectionRefusedException; import ibis.ipl.ConnectionTimedOutException; import ibis.ipl.Credentials; import ibis.ipl.IbisCapabilities; import ibis.ipl.IbisCreationFailedException; import ibis.ipl.IbisProperties; import ibis.ipl.IbisStarter; import ibis.ipl.MessageUpcall; import ibis.ipl.PortMismatchException; import ibis.ipl.PortType; import ibis.ipl.ReceivePortConnectUpcall; import ibis.ipl.RegistryEventHandler; import ibis.ipl.SendPortDisconnectUpcall; import ibis.ipl.impl.IbisIdentifier; import ibis.ipl.impl.ReceivePort; import ibis.ipl.impl.SendPortIdentifier; import ibis.ipl.support.Client; import ibis.smartsockets.hub.servicelink.ServiceLink; import ibis.smartsockets.virtual.VirtualServerSocket; import ibis.smartsockets.virtual.VirtualSocket; import ibis.smartsockets.virtual.VirtualSocketAddress; import ibis.smartsockets.virtual.VirtualSocketFactory; import ibis.util.ThreadPool; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.SocketTimeoutException; import java.util.HashMap; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class SmartSocketsIbis extends ibis.ipl.impl.Ibis implements Runnable, SmartSocketsProtocol { static final Logger logger = LoggerFactory .getLogger("ibis.ipl.impl.smartsockets.SmartSocketsIbis"); private VirtualSocketFactory factory; private VirtualServerSocket systemServer; private VirtualSocketAddress myAddress; private boolean quiting = false; private HashMap<ibis.ipl.IbisIdentifier, VirtualSocketAddress> addresses = new HashMap<ibis.ipl.IbisIdentifier, VirtualSocketAddress>(); private final HashMap<String, Object> lightConnection = new HashMap<String, Object>(); private final HashMap<String, Object> directConnection = new HashMap<String, Object>(); public SmartSocketsIbis(RegistryEventHandler registryEventHandler, IbisCapabilities capabilities, Credentials credentials, byte[] applicationTag, PortType[] types, Properties userProperties, IbisStarter starter) throws IbisCreationFailedException { super(registryEventHandler, capabilities, credentials, applicationTag, types, userProperties, starter); lightConnection.put("connect.module.allow", "ConnectModule(HubRouted)"); // directConnection.put("connect.module.skip", // "ConnectModule(HubRouted)"); // NOTE: this is too restrictive, since reverse connection setup also // result in a direct connection... directConnection.put("connect.module.allow", "ConnectModule(Direct)"); this.properties.checkProperties("ibis.ipl.impl.smartsockets.", new String[] { "ibis.ipl.impl.smartsockets" }, null, true); try { ServiceLink sl = factory.getServiceLink(); if (sl != null) { String colorString = ""; if (properties.getProperty(IbisProperties.LOCATION_COLOR) != null) { colorString = "^" + properties .getProperty(IbisProperties.LOCATION_COLOR); } sl.registerProperty("smartsockets.viz", "I^" + ident.name() + "^" + ident.name() + "," + ident.location().toString() + colorString); } } catch (Throwable e) { // ignored } // Create a new accept thread ThreadPool.createNew(this, "SmartSocketsIbis Accept Thread"); } protected byte[] getData() throws IOException { String clientID = this.properties.getProperty(ID_PROPERTY); Client client = Client.getOrCreateClient(clientID, properties, 0); factory = client.getFactory(); systemServer = factory.createServerSocket(0, 50, true, null); myAddress = systemServer.getLocalSocketAddress(); if (logger.isInfoEnabled()) { logger.info("--> SmartSocketIbis: address = " + myAddress); } return myAddress.toBytes(); } /* * // NOTE: this is wrong ? Even though the ibis has left, the * IbisIdentifier may still be floating around in the system... We should * just have some timeout on the cache entries instead... * * public void left(ibis.ipl.IbisIdentifier id) { super.left(id); * synchronized(addresses) { addresses.remove(id); } } * * public void died(ibis.ipl.IbisIdentifier id) { super.died(id); * synchronized(addresses) { addresses.remove(id); } } */ ServiceLink getServiceLink() { return factory.getServiceLink(); } VirtualSocket connect(SmartSocketsSendPort sp, ibis.ipl.impl.ReceivePortIdentifier rip, int timeout, boolean fillTimeout) throws IOException { IbisIdentifier id = (IbisIdentifier) rip.ibisIdentifier(); String name = rip.name(); VirtualSocketAddress idAddr; synchronized (addresses) { idAddr = addresses.get(id); if (idAddr == null) { idAddr = VirtualSocketAddress.fromBytes(id .getImplementationData(), 0); addresses.put(id, idAddr); } } long startTime = System.currentTimeMillis(); if (logger.isDebugEnabled()) { logger.debug("--> Creating socket for connection to " + name + " at " + idAddr); } PortType sendPortType = sp.getPortType(); do { DataOutputStream out = null; VirtualSocket s = null; int result = -1; try { HashMap<String, Object> h = null; if (sp.getPortType().hasCapability(PortType.CONNECTION_LIGHT)) { h = lightConnection; } else if (sp.getPortType().hasCapability( PortType.CONNECTION_DIRECT)) { h = directConnection; } /* * Map<String, String> properties = sp.managementProperties(); * * if (properties != null) { * * if (h == null) { h = new HashMap<String, Object>(); } * * h.putAll(properties); } * * if (logger.isDebugEnabled()) { * logger.debug("Creating connection with properties " + h); } */ s = factory.createClientSocket(idAddr, timeout, fillTimeout, h); s.setTcpNoDelay(true); out = new DataOutputStream( new BufferedArrayOutputStream(s.getOutputStream())); out.writeUTF(name); sp.getIdent().writeTo(out); sendPortType.writeTo(out); out.flush(); result = s.getInputStream().read(); switch (result) { case ReceivePort.ACCEPTED: return s; case ReceivePort.ALREADY_CONNECTED: throw new AlreadyConnectedException("Already connected", rip); case ReceivePort.TYPE_MISMATCH: // Read receiveport type from input, to produce a // better error message. DataInputStream in = new DataInputStream(s.getInputStream()); PortType rtp = new PortType(in); CapabilitySet s1 = rtp.unmatchedCapabilities(sendPortType); CapabilitySet s2 = sendPortType.unmatchedCapabilities(rtp); String message = ""; if (s1.size() != 0) { message = message + "\nUnmatched receiveport capabilities: " + s1.toString() + "."; } if (s2.size() != 0) { message = message + "\nUnmatched sendport capabilities: " + s2.toString() + "."; } throw new PortMismatchException( "Cannot connect ports of different port types." + message, rip); case ReceivePort.DENIED: throw new ConnectionRefusedException( "Receiver denied connection", rip); case ReceivePort.NO_MANY_TO_X: throw new ConnectionRefusedException( "Receiver already has a connection and neither ManyToOne not ManyToMany " + "is set", rip); case ReceivePort.NOT_PRESENT: case ReceivePort.DISABLED: // and try again if we did not reach the timeout... if (timeout > 0 && System.currentTimeMillis() > startTime + timeout) { throw new ConnectionTimedOutException( "Could not connect", rip); } break; case -1: throw new IOException("Encountered EOF in SmartSocketsIbis.connect"); default: throw new IOException("Illegal opcode in SmartSocketsIbis.connect"); } } catch (SocketTimeoutException e) { throw new ConnectionTimedOutException("Could not connect", rip); } finally { if (result != ReceivePort.ACCEPTED) { try { if (out != null) { out.close(); } } catch (Throwable e) { // ignored } try { s.close(); } catch (Throwable e) { // ignored } } } try { Thread.sleep(100); } catch (InterruptedException e) { // ignore } } while (true); } protected void quit() { try { quiting = true; // Connect so that the SmartSocketsIbis thread wakes up. factory.createClientSocket(myAddress, 0, false, null); } catch (Throwable e) { // Ignore } } private void handleConnectionRequest(VirtualSocket s) throws IOException { if (logger.isDebugEnabled()) { logger.debug("--> SmartSocketsIbis got connection request from " + s); } BufferedArrayInputStream bais = new BufferedArrayInputStream(s.getInputStream()); DataInputStream in = new DataInputStream(bais); OutputStream out = s.getOutputStream(); String name = in.readUTF(); SendPortIdentifier send = new SendPortIdentifier(in); PortType sp = new PortType(in); // First, lookup receiveport. SmartSocketsReceivePort rp = (SmartSocketsReceivePort) findReceivePort(name); int result; if (rp == null) { result = ReceivePort.NOT_PRESENT; out.write(result); out.close(); in.close(); s.close(); return; } synchronized(rp) { result = rp.connectionAllowed(send, sp); } if (logger.isDebugEnabled()) { logger.debug("--> S RP = " + name + ": " + ReceivePort.getString(result)); } out.write(result); if (result == ReceivePort.TYPE_MISMATCH) { DataOutputStream dout = new DataOutputStream(out); rp.getPortType().writeTo(dout); dout.flush(); } out.flush(); if (result == ReceivePort.ACCEPTED) { // add the connection to the receiveport. rp.connect(send, s, bais); if (logger.isDebugEnabled()) { logger.debug("--> S connect done "); } } else { out.close(); in.close(); s.close(); } } public void run() { // This thread handles incoming connection request from the // connect(SmartSocketsSendPort) call. boolean stop = false; while (!stop) { VirtualSocket s = null; if (logger.isDebugEnabled()) { logger.debug("--> SmartSocketsIbis doing new accept()"); } try { s = systemServer.accept(); s.setTcpNoDelay(true); } catch (Throwable e) { /* if the accept itself fails, we have a fatal problem. */ logger.error("SmartSocketsIbis:run: got fatal exception in accept! ", e); cleanup(); throw new Error("Fatal: SmartSocketsIbis could not do an accept", e); // This error is thrown in the SmartSocketsIbis thread, not in a user // thread. It kills the thread. } if (logger.isDebugEnabled()) { logger.debug("--> SmartSocketsIbis through new accept()"); } try { if (quiting) { s.close(); if (logger.isDebugEnabled()) { logger.debug("--> it is a quit: RETURN"); } cleanup(); return; } // This thread will now live on as a connection handler. Start // a new accept thread here, and make sure that this thread does // not do an accept again, if it ever returns to this loop. stop = true; try { Thread.currentThread().setName("Connection Handler"); } catch (Exception e) { // ignore } ThreadPool.createNew(this, "SmartSocketsIbis Accept Thread"); handleConnectionRequest(s); } catch (Throwable e) { try { s.close(); } catch (Throwable e2) { // ignored } logger.error("EEK: SmartSocketsIbis:run: got exception " + "(closing this socket only: ", e); } } } private void cleanup() { try { systemServer.close(); } catch (Throwable e) { // Ignore } } protected ibis.ipl.SendPort doCreateSendPort(PortType tp, String nm, SendPortDisconnectUpcall cU, Properties props) throws IOException { if (tp.hasCapability(PortType.CONNECTION_ULTRALIGHT)) { return new SmartSocketsUltraLightSendPort(this, tp, nm, props); } if (tp.hasCapability(PortType.CONNECTION_LIGHT)) { if (props == null) { props = new Properties(); } props.put("connect.module.type.skip", "direct"); } else if (tp.hasCapability(PortType.CONNECTION_DIRECT)) { if (props == null) { props = new Properties(); } props.put("connect.module.skip", "ConnectModule(HubRouted)"); } return new SmartSocketsSendPort(this, tp, nm, cU, props); } protected ibis.ipl.ReceivePort doCreateReceivePort(PortType tp, String nm, MessageUpcall u, ReceivePortConnectUpcall cU, Properties props) throws IOException { if (tp.hasCapability(PortType.CONNECTION_ULTRALIGHT)) { return new SmartSocketsUltraLightReceivePort(this, tp, nm, u, props); } if (tp.hasCapability(PortType.CONNECTION_LIGHT)) { if (props == null) { props = new Properties(); } props.put("connect.module.type.skip", "direct"); } else if (tp.hasCapability(PortType.CONNECTION_DIRECT)) { if (props == null) { props = new Properties(); } props.put("connect.module.skip", "ConnectModule(HubRouted)"); } return new SmartSocketsReceivePort(this, tp, nm, u, cU, props); } }