package ibis.ipl.registry.gossip; import ibis.ipl.registry.statistics.Statistics; import ibis.ipl.support.Connection; import ibis.smartsockets.virtual.VirtualSocketAddress; import ibis.smartsockets.virtual.VirtualSocketFactory; import java.io.IOException; import java.util.HashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of the ARRG algorithm * * @author ndrost * */ class ARRG extends Thread { private static final Logger logger = LoggerFactory.getLogger(ARRG.class); private static final int GOSSIP_TIMEOUT = 1000; private static final int CONNECT_TIMEOUT = 30000; private static final int SERVER_CONNECT_TIMEOUT = 120000; private static final int CACHE_SIZE = 100; private static final int GOSSIP_SIZE = 30; private static final long DEAD_TIMEOUT = 5 * 60 * 1000; private final VirtualSocketFactory socketFactory; private final VirtualSocketAddress bootstrapAddress; private final String poolName; private final Statistics statistics; private final ARRGCacheEntry self; private final ARRGCache cache; private final ARRGCache fallbackCache; private boolean ended; private long lastGossip; ARRG(VirtualSocketAddress address, boolean arrgOnly, VirtualSocketAddress[] bootstrapList, VirtualSocketAddress bootstrapAddress, String poolName, VirtualSocketFactory socketFactory, Statistics statistics) { this.socketFactory = socketFactory; this.poolName = poolName; this.bootstrapAddress = bootstrapAddress; this.statistics = statistics; self = new ARRGCacheEntry(address, arrgOnly); cache = new ARRGCache(CACHE_SIZE); fallbackCache = new ARRGCache(CACHE_SIZE); // add all bootstrap addresses to both caches for (VirtualSocketAddress bootstrapEntry : bootstrapList) { ARRGCacheEntry entry = new ARRGCacheEntry(bootstrapEntry, true); cache.add(entry); fallbackCache.add(entry); } lastGossip = System.currentTimeMillis(); } /* * (non-Javadoc) * * @see java.lang.Thread#start() */ @Override public synchronized void start() { super.setDaemon(true); super.start(); } public String getPoolName() { return poolName; } synchronized void end() { ended = true; notifyAll(); } synchronized boolean ended() { return ended; } private synchronized void resetLastGossip() { lastGossip = System.currentTimeMillis(); } /** * If we have not had any sucessful gossip for DEAD_TIMEOUT, declare this * pool dead * * @return true if this pool is dead */ synchronized boolean isDead() { return System.currentTimeMillis() > lastGossip + DEAD_TIMEOUT; } void handleGossip(Connection connection) throws IOException { ARRGCacheEntry peerEntry = new ARRGCacheEntry(connection.in()); int receiveCount = connection.in().readInt(); ARRGCacheEntry[] receivedEntries = new ARRGCacheEntry[receiveCount]; for (int i = 0; i < receiveCount; i++) { receivedEntries[i] = new ARRGCacheEntry(connection.in()); } connection.sendOKReply(); self.writeTo(connection.out()); ARRGCacheEntry[] sendEntries = cache.getRandomEntries(GOSSIP_SIZE, true); connection.out().writeInt(sendEntries.length); for (ARRGCacheEntry entry : sendEntries) { entry.writeTo(connection.out()); } connection.out().flush(); connection.close(); cache.add(peerEntry); cache.add(receivedEntries); resetLastGossip(); logger.debug("bootstrap service for " + poolName + " received request from " + peerEntry); } private void gossip(VirtualSocketAddress victim, int timeout, boolean fillTimeout) throws IOException { long start = System.currentTimeMillis(); if (victim == null) { logger.debug("no victim specified"); return; } if (victim.equals(self.getAddress())) { logger.debug("not gossiping with outselves"); return; } logger.debug("gossiping with " + victim); ARRGCacheEntry[] sendEntries = cache.getRandomEntries(GOSSIP_SIZE, true); Connection connection = null; try { connection = new Connection(victim, timeout, fillTimeout, socketFactory); // header connection.out().writeByte(Protocol.MAGIC_BYTE); connection.out().writeByte(Protocol.OPCODE_ARRG_GOSSIP); connection.out().writeUTF(poolName); // data self.writeTo(connection.out()); connection.out().writeInt(sendEntries.length); for (ARRGCacheEntry entry : sendEntries) { entry.writeTo(connection.out()); } connection.getAndCheckReply(); ARRGCacheEntry peerEntry = new ARRGCacheEntry(connection.in()); int receiveCount = connection.in().readInt(); ARRGCacheEntry[] receivedEntries = new ARRGCacheEntry[receiveCount]; for (int i = 0; i < receiveCount; i++) { receivedEntries[i] = new ARRGCacheEntry(connection.in()); } connection.close(); cache.add(peerEntry); cache.add(receivedEntries); resetLastGossip(); if (statistics != null) { statistics.add(Protocol.OPCODE_ARRG_GOSSIP, System.currentTimeMillis() - start, connection.read(), connection.written(), false); } } finally { if (connection != null) { connection.close(); } } } VirtualSocketAddress getRandomMember() { ARRGCacheEntry result = cache.getRandomEntry(false); if (result == null) { return null; } return result.getAddress(); } VirtualSocketAddress[] getRandomMembers(int size) { // use a set to get rid of duplicates HashSet<VirtualSocketAddress> result = new HashSet<VirtualSocketAddress>(); ARRGCacheEntry[] entries = cache.getRandomEntries(size, false); for (ARRGCacheEntry entry : entries) { if (!entry.isArrgOnly()) { result.add(entry.getAddress()); } } return result.toArray(new VirtualSocketAddress[0]); } VirtualSocketAddress[] getMembers() { // use a set to get rid of duplicates HashSet<VirtualSocketAddress> result = new HashSet<VirtualSocketAddress>(); ARRGCacheEntry[] entries = cache.getEntries(false); for (ARRGCacheEntry entry : entries) { if (!entry.isArrgOnly()) { result.add(entry.getAddress()); } } entries = fallbackCache.getEntries(false); for (ARRGCacheEntry entry : entries) { if (!entry.isArrgOnly()) { result.add(entry.getAddress()); } } return result.toArray(new VirtualSocketAddress[0]); } public void run() { while (!ended()) { ARRGCacheEntry victim = cache.getRandomEntry(true); boolean success = false; // first try normal cache if (victim != null) { try { gossip(victim.getAddress(), CONNECT_TIMEOUT, false); fallbackCache.add(victim); success = true; } catch (IOException e) { logger.debug("could not gossip with " + victim, e); } } // then try fallback cache if (success == false) { victim = fallbackCache.getRandomEntry(true); if (victim != null) { try { gossip(victim.getAddress(), CONNECT_TIMEOUT, false); success = true; } catch (IOException e) { logger.debug("could not gossip with fallback entry: " + victim, e); } } } // lastly, use bootstrap service (also wait longer on connecting) if (success == false) { if (bootstrapAddress != null) { try { gossip(bootstrapAddress, SERVER_CONNECT_TIMEOUT, true); success = true; } catch (IOException e) { logger.error( "could not gossip with bootstrap server at " + bootstrapAddress, e); } } } synchronized (this) { long timeout = (long) (Math.random() * GOSSIP_TIMEOUT) + 1; try { logger.debug("waiting " + timeout + " ms"); wait(timeout); } catch (InterruptedException e) { // IGNORE } } } } public Statistics getStatistics() { return statistics; } }