package ibis.ipl.registry.central.server; import ibis.io.Conversion; import ibis.ipl.Credentials; import ibis.ipl.impl.IbisIdentifier; import ibis.ipl.impl.Location; import ibis.ipl.registry.ControlPolicy; import ibis.ipl.registry.central.Member; import ibis.ipl.registry.central.Protocol; import ibis.ipl.server.ServerProperties; import ibis.ipl.support.Connection; import ibis.smartsockets.virtual.VirtualServerSocket; import ibis.smartsockets.virtual.VirtualSocketFactory; import ibis.util.ThreadPool; import java.io.IOException; import java.security.AccessControlException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; final class ServerConnectionHandler implements Runnable { private static final int CONNECTION_BACKLOG = 50; static final int MAX_THREADS = 50; private static final Logger logger = LoggerFactory .getLogger(ServerConnectionHandler.class); private final CentralRegistryService server; private final VirtualSocketFactory socketFactory; private final VirtualServerSocket serverSocket; private int currentNrOfThreads = 0; private int maxNrOfThreads = 0; private ControlPolicy policy; ServerConnectionHandler(CentralRegistryService server, VirtualSocketFactory connectionFactory, ControlPolicy policy) throws IOException { this.server = server; this.socketFactory = connectionFactory; serverSocket = socketFactory.createServerSocket( Protocol.VIRTUAL_PORT, CONNECTION_BACKLOG, null); this.policy = policy; createThread(); } private Pool handleJoin(Connection connection) throws Exception { Member member; Pool pool; // long start = System.currentTimeMillis(); String version = ServerProperties.implementationVersion; String peerVersion = connection.in().readUTF(); if (peerVersion == null || !peerVersion.equals(version)) { throw new IOException("Wrong ipl server version in join: " + peerVersion + ", should be " + version); } int length = connection.in().readInt(); if (length < 0) { throw new IOException("unexpected end of data on join"); } byte[] clientAddress = new byte[length]; connection.in().readFully(clientAddress); String poolName = connection.in().readUTF(); length = connection.in().readInt(); if (length < 0) { throw new IOException("unexpected end of data on join"); } byte[] implementationData = new byte[length]; connection.in().readFully(implementationData); String implementationVersion = connection.in().readUTF(); Location location = new Location(connection.in()); boolean peerBootstrap = connection.in().readBoolean(); long heartbeatInterval = connection.in().readLong(); long eventPushInterval = connection.in().readLong(); boolean gossip = connection.in().readBoolean(); long gossipInterval = connection.in().readLong(); boolean adaptGossipInterval = connection.in().readBoolean(); boolean tree = connection.in().readBoolean(); boolean closedWorld = connection.in().readBoolean(); int poolSize = connection.in().readInt(); boolean keepStatistics = connection.in().readBoolean(); long statisticsInterval = connection.in().readLong(); boolean purgeHistory = connection.in().readBoolean(); length = connection.in().readInt(); if (length < 0) { throw new IOException("unexpected end of data on join"); } byte[] credentialBytes = new byte[length]; connection.in().readFully(credentialBytes); Credentials credentials = (Credentials) Conversion .byte2object(credentialBytes); length = connection.in().readInt(); byte[] applicationTag = null; if (length >= 0) { applicationTag = new byte[length]; connection.in().readFully(applicationTag); } if (policy != null) { try { policy.onJoin(credentials); } catch (AccessControlException e) { connection.closeWithError(e.getMessage()); throw e; } } // long dataRead = System.currentTimeMillis(); pool = server.getOrCreatePool(poolName, peerBootstrap, heartbeatInterval, eventPushInterval, gossip, gossipInterval, adaptGossipInterval, tree, closedWorld, poolSize, keepStatistics, statisticsInterval, purgeHistory, implementationVersion); // long poolRetrieved = System.currentTimeMillis(); try { member = pool.join(implementationData, clientAddress, location, implementationVersion, applicationTag); } catch (IOException e) { connection.closeWithError(e.getMessage()); throw e; } // long joinDone = System.currentTimeMillis(); connection.sendOKReply(); // write info on new member (including identifier, join time and // current minimum time of pool member.getIbis().writeTo(connection.out()); connection.out().writeInt(member.getEvent().getTime()); connection.out().writeInt(member.getCurrentTime()); pool.writeBootstrapList(connection.out()); connection.out().flush(); // long dataWritten = System.currentTimeMillis(); pool.gotHeartbeat(member.getIbis()); // logger.info("dataRead = " + (start - dataRead) + ", poolRetrieved = " // + (poolRetrieved - dataRead) + ", joinDone = " + (joinDone - // poolRetrieved) + ", datawritten = " + (dataWritten - joinDone)); return pool; } private Pool handleLeave(Connection connection) throws Exception { IbisIdentifier identifier = new IbisIdentifier(connection.in()); Pool pool = server.getPool(identifier.poolName()); if (pool == null) { connection.closeWithError("pool not found"); return null; } try { pool.leave(identifier); } catch (Exception e) { connection.closeWithError(e.toString()); throw e; } connection.sendOKReply(); pool.gotHeartbeat(identifier); return pool; } private Pool handleElect(Connection connection) throws Exception { IbisIdentifier candidate = new IbisIdentifier(connection.in()); String election = connection.in().readUTF(); Pool pool = server.getPool(candidate.poolName()); if (pool == null) { connection.closeWithError("pool not found"); throw new Exception("pool " + candidate.poolName() + " not found"); } IbisIdentifier winner = pool.elect(election, candidate); connection.sendOKReply(); winner.writeTo(connection.out()); pool.gotHeartbeat(candidate); return pool; } private Pool handleGetSequenceNumber(Connection connection) throws Exception { IbisIdentifier identifier = new IbisIdentifier(connection.in()); String name = connection.in().readUTF(); Pool pool = server.getPool(identifier.poolName()); if (pool == null) { connection.closeWithError("pool not found"); throw new Exception("pool " + identifier.poolName() + " not found"); } long number = pool.getSequenceNumber(name); connection.sendOKReply(); connection.out().writeLong(number); pool.gotHeartbeat(identifier); return pool; } private Pool handleDead(Connection connection) throws Exception { IbisIdentifier identifier = new IbisIdentifier(connection.in()); IbisIdentifier corpse = new IbisIdentifier(connection.in()); Pool pool = server.getPool(identifier.poolName()); if (pool == null) { connection.closeWithError("pool not found"); throw new Exception("pool " + identifier.poolName() + " not found"); } try { pool.dead(corpse, new Exception("ibis declared dead by " + identifier)); } catch (Exception e) { connection.closeWithError(e.getMessage()); throw e; } connection.sendOKReply(); pool.gotHeartbeat(identifier); return pool; } private Pool handleMaybeDead(Connection connection) throws Exception { IbisIdentifier identifier = new IbisIdentifier(connection.in()); IbisIdentifier suspect = new IbisIdentifier(connection.in()); Pool pool = server.getPool(identifier.poolName()); if (pool == null) { connection.closeWithError("pool not found"); throw new Exception("pool " + identifier.poolName() + " not found"); } pool.maybeDead(suspect); connection.sendOKReply(); pool.gotHeartbeat(identifier); return pool; } private Pool handleSignal(Connection connection) throws Exception { IbisIdentifier identifier = new IbisIdentifier(connection.in()); String signal = connection.in().readUTF(); IbisIdentifier[] receivers = new IbisIdentifier[connection.in() .readInt()]; for (int i = 0; i < receivers.length; i++) { receivers[i] = new IbisIdentifier(connection.in()); } Pool pool = server.getPool(identifier.poolName()); if (pool == null) { connection.closeWithError("pool not found"); throw new Exception("pool " + identifier.poolName() + " not found"); } pool.signal(signal, identifier, receivers); connection.sendOKReply(); pool.gotHeartbeat(identifier); return pool; } private Pool handleGetState(Connection connection) throws Exception { IbisIdentifier identifier = new IbisIdentifier(connection.in()); int joinTime = connection.in().readInt(); Pool pool = server.getPool(identifier.poolName()); if (pool == null) { connection.closeWithError("pool not found"); throw new Exception("pool " + identifier.poolName() + " not found"); } connection.sendOKReply(); pool.writeState(connection.out(), joinTime); connection.out().flush(); pool.gotHeartbeat(identifier); return pool; } private Pool handleHeartbeat(Connection connection) throws Exception { IbisIdentifier identifier = new IbisIdentifier(connection.in()); Pool pool = server.getPool(identifier.poolName()); if (pool == null) { connection.closeWithError("pool not found"); throw new Exception("pool " + identifier.poolName() + " not found"); } connection.sendOKReply(); pool.gotHeartbeat(identifier); return pool; } private Pool handleTerminate(Connection connection) throws Exception { IbisIdentifier source = new IbisIdentifier(connection.in()); Pool pool = server.getPool(source.poolName()); if (pool == null) { connection.closeWithError("pool not found"); throw new Exception("pool " + source.poolName() + " not found"); } pool.terminate(source); connection.sendOKReply(); pool.gotHeartbeat(source); return pool; } private synchronized void createThread() { while (currentNrOfThreads >= MAX_THREADS) { try { wait(); } catch (InterruptedException e) { // IGNORE } } // create new thread for next connection ThreadPool.createNew(this, "server connection handler"); currentNrOfThreads++; if (currentNrOfThreads > maxNrOfThreads) { maxNrOfThreads = currentNrOfThreads; } logger.debug("Now " + currentNrOfThreads + " connections"); } private synchronized void threadEnded() { currentNrOfThreads--; notifyAll(); } public void run() { Connection connection = null; try { logger.debug("accepting connection"); connection = new Connection(serverSocket); logger.debug("connection accepted"); } catch (IOException e) { if (server.isStopped()) { threadEnded(); return; } logger.error("Accept failed, waiting a second, will retry", e); // wait a bit try { Thread.sleep(1000); } catch (InterruptedException e1) { // IGNORE } } createThread(); if (connection == null) { threadEnded(); return; } long start = System.currentTimeMillis(); byte opcode = 0; Pool pool = null; try { byte magic = connection.in().readByte(); if (magic != Protocol.MAGIC_BYTE) { throw new IOException( "Invalid header byte in accepting connection"); } opcode = connection.in().readByte(); if (logger.isDebugEnabled() && opcode < Protocol.NR_OF_OPCODES) { logger.debug("got request, opcode = " + Protocol.OPCODE_NAMES[opcode]); } switch (opcode) { case Protocol.OPCODE_JOIN: pool = handleJoin(connection); break; case Protocol.OPCODE_LEAVE: pool = handleLeave(connection); break; case Protocol.OPCODE_ELECT: pool = handleElect(connection); break; case Protocol.OPCODE_SEQUENCE_NR: pool = handleGetSequenceNumber(connection); break; case Protocol.OPCODE_DEAD: pool = handleDead(connection); break; case Protocol.OPCODE_MAYBE_DEAD: pool = handleMaybeDead(connection); break; case Protocol.OPCODE_SIGNAL: pool = handleSignal(connection); break; case Protocol.OPCODE_GET_STATE: pool = handleGetState(connection); break; case Protocol.OPCODE_HEARTBEAT: pool = handleHeartbeat(connection); break; case Protocol.OPCODE_TERMINATE: pool = handleTerminate(connection); break; default: logger.error("unknown opcode: " + opcode); } } catch (Exception e) { // send error to client connection.closeWithError("Server: " + e.getMessage()); logger.error("error on handling connection", e); } finally { connection.close(); } if (pool != null) { if (pool.getStatistics() != null) { pool.getStatistics().add(opcode, System.currentTimeMillis() - start, connection.read(), connection.written(), true); logger.debug("done handling request"); } if (pool.hasEnded()) { // save statistics pool.saveStatistics(); } } threadEnded(); } public void end() { try { serverSocket.close(); } catch (Exception e) { // IGNORE } if (logger.isInfoEnabled()) { synchronized (this) { logger.info("max simultanious connections was: " + maxNrOfThreads); } } } }