package com.limegroup.gnutella; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Collections; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.limegroup.gnutella.messages.PingRequest; import com.limegroup.gnutella.settings.ConnectionSettings; /* * A "watchdog" that periodically examines connections and * replaces dud connections with better ones. There are a number of * possible heuristics to use when examining connections. */ public final class ConnectionWatchdog { private static final Log LOG = LogFactory.getLog(ConnectionWatchdog.class); /** * Constant handle to single <tt>ConnectionWatchdog</tt> instance, * following the singleton pattern. */ private static final ConnectionWatchdog INSTANCE = new ConnectionWatchdog(); /** How long (in msec) a connection can be a dud (see below) before being booted. */ private static final int EVALUATE_TIME=30000; /** Additional time (in msec) to wait before rechecking connections. */ private static final int REEVALUATE_TIME=15000; /** * Singleton accessor for <tt>ConnectionWatchdog</tt> instance. */ public static ConnectionWatchdog instance() { return INSTANCE; } /** * Creates a new <tt>ConnectionWatchdog</tt> instance to monitor * connections to make sure they are still up and responding well. * * @param manager the <tt>ConnectionManager</tt> instance that provides * access to the list of connections to monitor */ private ConnectionWatchdog() {} /** * Starts the <tt>ConnectionWatchdog</tt>. */ public void start() { findDuds(); } /** A snapshot of a connection. */ private static class ConnectionState { final long sentDropped; final long sent; final long received; /** Takes a snapshot of the given connection. */ ConnectionState(ManagedConnection c) { this.sentDropped=c.getNumSentMessagesDropped(); this.sent=c.getNumMessagesSent(); this.received=c.getNumMessagesReceived(); } /** * Returns true if the state of this connection has not * made sufficient progress since the old snapshot was taken. */ boolean notProgressedSince(ConnectionState old) { //Current policy: returns true if (a) all packets sent since //snapshot were dropped or (b) we have received no data. long numSent=this.sent-old.sent; long numSentDropped=this.sentDropped-old.sentDropped; long numReceived=this.received-old.received; if ((numSent==numSentDropped) && numSent!=0) { return true; } else if (numReceived==0) { return true; } else return false; } public String toString() { return "{sent: "+sent+", sdropped: "+sentDropped+"}"; } } /** * Schedules a snapshot of connection progress to be evaluated for duds. */ private void findDuds() { //Take a snapshot of all connections, including leaves. Map /* ManagedConnection -> ConnectionState */ snapshot = new HashMap(); for (Iterator iter = allConnections(); iter.hasNext(); ) { ManagedConnection c=(ManagedConnection)iter.next(); if (!c.isKillable()) continue; snapshot.put(c, new ConnectionState(c)); } RouterService.schedule(new DudChecker(snapshot, false), EVALUATE_TIME, 0); } /** * Looks at a list of connections & pings them, waiting a certain amount of * time for a response. If no messages are exchanged on the connection in * that time, the connection is killed. * * This is done by scheduling an event and checking the progress against * a snapshot. * @requires connections is a list of ManagedConnection * @modifies manager, router * @effects removes from manager any ManagedConnection's in "connections" * that still aren't progressing after being pinged. */ private void killIfStillDud(List connections) { //Take a snapshot of each connection, then send a ping. //The horizon statistics for the connection are temporarily disabled //during this process. In the rare chance that legitimate pongs //(other than in response to my ping), they will be ignored. HashMap /* Connection -> ConnectionState */ snapshot = new HashMap(); for (Iterator iter = connections.iterator(); iter.hasNext(); ) { ManagedConnection c=(ManagedConnection)iter.next(); if (!c.isKillable()) continue; snapshot.put(c, new ConnectionState(c)); RouterService.getMessageRouter().sendPingRequest(new PingRequest((byte)1), c); } RouterService.schedule(new DudChecker(snapshot, true), REEVALUATE_TIME, 0); } /** Returns an iterator of all initialized connections in this, including * leaf connecions. */ private Iterator allConnections() { List normal = RouterService.getConnectionManager().getInitializedConnections(); List leaves = RouterService.getConnectionManager().getInitializedClientConnections(); List buf = new ArrayList(normal.size() + leaves.size()); buf.addAll(normal); buf.addAll(leaves); return buf.iterator(); } /** * Determines if snapshots of connections are duds. * If 'kill' is true, if they're a dud they're immediately clue. * Otherwise, duds are queued up for additional checking. * If no duds exist (or they were killed), findDuds() is started again. */ private class DudChecker implements Runnable { private Map snapshots; private boolean kill; /** * Constructs a new DudChecker with the snapshots of ConnectionStates. * The checker may be used to kill the connections (if they haven't progressed) * or to re-evaluate them later. */ DudChecker(Map snapshots, boolean kill) { this.snapshots = snapshots; this.kill = kill; } public void run() { //Loop through all connections, trying to find ones that //have not made sufficient progress. List potentials = kill ? Collections.EMPTY_LIST : new ArrayList(); for (Iterator iter=allConnections(); iter.hasNext(); ) { ManagedConnection c = (ManagedConnection)iter.next(); if (!c.isKillable()) continue; Object state = snapshots.get(c); if (state == null) continue; //this is a new connection ConnectionState currentState=new ConnectionState(c); ConnectionState oldState=(ConnectionState)state; if (currentState.notProgressedSince(oldState)) { if(kill) { if(ConnectionSettings.WATCHDOG_ACTIVE.getValue()) { if(LOG.isWarnEnabled()) LOG.warn("Killing connection: " + c); RouterService.removeConnection(c); } } else { if(LOG.isWarnEnabled()) LOG.warn("Potential dud: " + c); potentials.add(c); } } } if(potentials.isEmpty()) findDuds(); else killIfStillDud(potentials); } } }