package com.limegroup.gnutella; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.core.settings.ConnectionSettings; import org.limewire.inject.EagerSingleton; import org.limewire.lifecycle.Service; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.name.Named; import com.limegroup.gnutella.connection.RoutedConnection; import com.limegroup.gnutella.messages.PingRequest; import com.limegroup.gnutella.messages.PingRequestFactory; /* * 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. */ @EagerSingleton public final class ConnectionWatchdog implements Service { private static final Log LOG = LogFactory.getLog(ConnectionWatchdog.class); /** 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; private final ScheduledExecutorService backgroundExecutor; private final Provider<MessageRouter> messageRouter; private final Provider<ConnectionManager> connectionManager; private final ConnectionServices connectionServices; private final PingRequestFactory pingRequestFactory; @Inject public ConnectionWatchdog( @Named("backgroundExecutor") ScheduledExecutorService backgroundExecutor, Provider<MessageRouter> messageRouter, Provider<ConnectionManager> connectionManager, ConnectionServices connectionServices, PingRequestFactory pingRequestFactory) { this.backgroundExecutor = backgroundExecutor; this.messageRouter = messageRouter; this.connectionManager = connectionManager; this.connectionServices = connectionServices; this.pingRequestFactory = pingRequestFactory; } @Inject void register(org.limewire.lifecycle.ServiceRegistry registry) { registry.register(this); } public String getServiceName() { return org.limewire.i18n.I18nMarker.marktr("Stale Connection Management"); } public void initialize() { } public void stop() { } /** * 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(RoutedConnection c) { this.sentDropped=c.getConnectionMessageStatistics().getNumSentMessagesDropped(); this.sent=c.getConnectionMessageStatistics().getNumMessagesSent(); this.received=c.getConnectionMessageStatistics().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; } @Override 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<RoutedConnection, ConnectionState> snapshot = new HashMap<RoutedConnection, ConnectionState>(); for(RoutedConnection c : allConnections()) { if (!c.isKillable()) continue; snapshot.put(c, new ConnectionState(c)); } backgroundExecutor.schedule(new DudChecker(snapshot, false), EVALUATE_TIME, TimeUnit.MILLISECONDS); } /** * 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 RoutedConnection * @modifies manager, router * @effects removes from manager any RoutedConnection's in "connections" * that still aren't progressing after being pinged. */ private void killIfStillDud(List<? extends RoutedConnection> 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. Map<RoutedConnection, ConnectionState> snapshot = new HashMap<RoutedConnection, ConnectionState>(); for(RoutedConnection c : connections) { if (!c.isKillable()) continue; snapshot.put(c, new ConnectionState(c)); PingRequest ping = pingRequestFactory.createPingRequest((byte)1); messageRouter.get().sendPingRequest(ping, c); } backgroundExecutor.schedule(new DudChecker(snapshot, true), REEVALUATE_TIME, TimeUnit.MILLISECONDS); } /** Returns an iterable of all initialized connections in this, including * leaf connecions. */ private Iterable<RoutedConnection> allConnections() { List<RoutedConnection> normal = connectionManager.get().getInitializedConnections(); List<RoutedConnection> leaves = connectionManager.get().getInitializedClientConnections(); List<RoutedConnection> buf = new ArrayList<RoutedConnection>(normal.size() + leaves.size()); buf.addAll(normal); buf.addAll(leaves); return buf; } /** * 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<RoutedConnection, ConnectionState> 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<RoutedConnection, ConnectionState> 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<RoutedConnection> potentials; if(kill) potentials = Collections.emptyList(); else potentials = new ArrayList<RoutedConnection>(); for(RoutedConnection c : allConnections()) { if (!c.isKillable()) continue; ConnectionState oldState = snapshots.get(c); if (oldState == null) continue; //this is a new connection ConnectionState currentState=new ConnectionState(c); if (currentState.notProgressedSince(oldState)) { if(kill) { if(ConnectionSettings.WATCHDOG_ACTIVE.getValue()) { if(LOG.isWarnEnabled()) LOG.warn("Killing connection: " + c); connectionServices.removeConnection(c); } } else { if(LOG.isWarnEnabled()) LOG.warn("Potential dud: " + c); potentials.add(c); } } } if(potentials.isEmpty()) findDuds(); else killIfStillDud(potentials); } } }