/* $Id$ */ package ibis.ipl.impl.tcp; 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.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.SendPort; import ibis.ipl.impl.SendPortIdentifier; 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 TcpIbis extends ibis.ipl.impl.Ibis implements Runnable, TcpProtocol { static final Logger logger = LoggerFactory .getLogger("ibis.ipl.impl.tcp.TcpIbis"); private IbisSocketFactory factory; private IbisServerSocket systemServer; private IbisSocketAddress myAddress; private boolean quiting = false; private HashMap<ibis.ipl.IbisIdentifier, IbisSocketAddress> addresses = new HashMap<ibis.ipl.IbisIdentifier, IbisSocketAddress>(); public TcpIbis(RegistryEventHandler registryEventHandler, IbisCapabilities capabilities, Credentials credentials, byte[] applicationTag, PortType[] types, Properties userProperties, IbisStarter starter) throws IbisCreationFailedException { super(registryEventHandler, capabilities, credentials, applicationTag, types, userProperties, starter); this.properties.checkProperties("ibis.ipl.impl.tcp.", new String[] { }, null, true); factory.setIdent(ident); // Create a new accept thread ThreadPool.createNew(this, "TcpIbis Accept Thread"); } protected byte[] getData() throws IOException { factory = new IbisSocketFactory(properties); systemServer = factory.createServerSocket(0, 50, true, null); myAddress = systemServer.getLocalSocketAddress(); if (logger.isInfoEnabled()) { logger.info("--> TcpIbis: 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); } } */ IbisSocket connect(TcpSendPort sp, ibis.ipl.impl.ReceivePortIdentifier rip, int timeout, boolean fillTimeout) throws IOException { IbisIdentifier id = (IbisIdentifier) rip.ibisIdentifier(); String name = rip.name(); IbisSocketAddress idAddr; synchronized (addresses) { idAddr = addresses.get(id); if (idAddr == null) { idAddr = new IbisSocketAddress(id.getImplementationData()); 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; IbisSocket s = null; int result = -1; try { s = factory.createClientSocket(idAddr, timeout, fillTimeout, sp .managementProperties()); 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 TcpIbis.connect"); default: throw new IOException("Illegal opcode in TcpIbis.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 TcpIbis thread wakes up. factory.createClientSocket(myAddress, 0, false, null); } catch (Throwable e) { // Ignore } } private void handleConnectionRequest(IbisSocket s) throws IOException { if (logger.isDebugEnabled()) { logger.debug("--> TcpIbis 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. TcpReceivePort rp = (TcpReceivePort) findReceivePort(name); int result; if (rp == null) { result = ReceivePort.NOT_PRESENT; } else { 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(TcpSendPort) call. boolean stop = false; while (!stop) { IbisSocket s = null; if (logger.isDebugEnabled()) { logger.debug("--> TcpIbis 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("TcpIbis:run: got fatal exception in accept! ", e); cleanup(); throw new Error("Fatal: TcpIbis could not do an accept", e); // This error is thrown in the TcpIbis thread, not in a user // thread. It kills the thread. } if (logger.isDebugEnabled()) { logger.debug("--> TcpIbis 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, "TcpIbis Accept Thread"); // Try to get the accept thread into an accept call. (Ceriel) // Thread.currentThread().yield(); // // Yield is evil. It breaks the whole concept of starting a // replacement thread and handling the incoming request // ourselves. -- Jason handleConnectionRequest(s); } catch (Throwable e) { try { s.close(); } catch (Throwable e2) { // ignored } logger.error("EEK: TcpIbis:run: got exception " + "(closing this socket only: ", e); } } } private void cleanup() { try { systemServer.close(); } catch (Throwable e) { // Ignore } } protected SendPort doCreateSendPort(PortType tp, String nm, SendPortDisconnectUpcall cU, Properties props) throws IOException { return new TcpSendPort(this, tp, nm, cU, props); } protected ReceivePort doCreateReceivePort(PortType tp, String nm, MessageUpcall u, ReceivePortConnectUpcall cU, Properties props) throws IOException { return new TcpReceivePort(this, tp, nm, u, cU, props); } }