/* * Mojito Distributed Hash Table (Mojito DHT) * Copyright (C) 2006-2007 LimeWire LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.limewire.mojito.routing; import java.util.Collection; import java.util.Iterator; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.mojito.Context; import org.limewire.mojito.KUID; import org.limewire.mojito.concurrent.DHTFuture; import org.limewire.mojito.concurrent.DHTFutureAdapter; import org.limewire.mojito.concurrent.DHTFutureListener; import org.limewire.mojito.manager.BootstrapManager; import org.limewire.mojito.result.FindNodeResult; import org.limewire.mojito.result.PingResult; import org.limewire.mojito.routing.RouteTable.SelectMode; import org.limewire.mojito.settings.BucketRefresherSettings; import org.limewire.mojito.settings.KademliaSettings; /** * The BucketRefresher goes in periodic intervals through all Buckets * and refreshes every Bucket that hasn't been touched for a certain * amount of time. */ public class BucketRefresher implements Runnable { private static final Log LOG = LogFactory.getLog(BucketRefresher.class); private final Context context; private final RefreshTask refreshTask = new RefreshTask(); private ScheduledFuture future; public BucketRefresher(Context context) { this.context = context; } /** * Starts the BucketRefresher. */ public void start() { synchronized (refreshTask) { if (future == null) { long delay = BucketRefresherSettings.BUCKET_REFRESHER_DELAY.getValue(); long initialDelay = delay; if (BucketRefresherSettings.UNIFORM_BUCKET_REFRESH_DISTRIBUTION.getValue()) { initialDelay = delay + (long)(delay * Math.random()); } future = context.getDHTExecutorService() .scheduleWithFixedDelay(this, initialDelay, delay, TimeUnit.MILLISECONDS); } } } /** * Stops the BucketRefresher. */ public void stop() { synchronized (refreshTask) { if (future != null) { future.cancel(true); future = null; } refreshTask.stop(); } } public void run() { synchronized (refreshTask) { if(LOG.isTraceEnabled()) { LOG.trace("Random bucket refresh"); } // Running the BucketRefresher w/o being bootstrapped is // pointless. Try to bootstrap from the RouteTable if // it's possible. BootstrapManager bootstrapManager = context.getBootstrapManager(); synchronized (bootstrapManager) { if (!bootstrapManager.isBootstrapped()) { if (!bootstrapManager.isBootstrapping()) { if (LOG.isInfoEnabled()) { LOG.info("Bootstrap " + context.getName()); } // If we are not bootstrapped and have some // Nodes in our RouteTable, try to bootstrap // from the RouteTable DHTFutureListener<PingResult> listener = new DHTFutureAdapter<PingResult>() { @Override public void handleFutureSuccess(PingResult result) { context.bootstrap(result.getContact()); } }; DHTFuture<PingResult> future = context.findActiveContact(); future.addDHTFutureListener(listener); } else { if (LOG.isInfoEnabled()) { LOG.info(context.getName() + " is bootstrapping"); } } // In any case exit here! return; } } // Refresh but make sure the task from the previous // run() call has finished as we don't want to run // refresher tasks in parallel if (refreshTask.isDone()) { long pingNearest = BucketRefresherSettings.BUCKET_REFRESHER_PING_NEAREST.getValue(); if (pingNearest > 0L) { Collection<Contact> nodes = context.getRouteTable().select( context.getLocalNodeID(), KademliaSettings.REPLICATION_PARAMETER.getValue(), SelectMode.ALL); for (Contact node : nodes) { if (context.isLocalNode(node)) { continue; } // Ping only Nodes that haven't responded/contacted us // for a certain amount of time long timeStamp = node.getTimeStamp(); if ((System.currentTimeMillis() - timeStamp) >= pingNearest) { context.ping(node); } } } refreshTask.refresh(); } } } /** * The RefreshTask iterates one-by-one through a List of KUIDs * and does a lookup for the ID. Every time a lookup finishes it * starts a new lookup for the next ID until all KUIDs have been * looked up. */ private class RefreshTask implements DHTFutureListener<FindNodeResult> { private Iterator<KUID> bucketIds = null; private DHTFuture<FindNodeResult> future = null; /** * Returns whether or not the refresh task has * finished (initial state is true) */ public synchronized boolean isDone() { return bucketIds == null || !bucketIds.hasNext(); } /** * Stops the RefreshTask. */ public synchronized void stop() { if (future != null) { future.cancel(true); future = null; } bucketIds = null; } /** * Starts the refresh. */ public synchronized boolean refresh() { Collection<KUID> list = context.getRouteTable().getRefreshIDs(false); if (LOG.isInfoEnabled()) { LOG.info(context.getName() + " has " + list.size() + " Buckets to refresh"); } bucketIds = list.iterator(); return next(); } /** * Lookup the next KUID. */ private synchronized boolean next() { if (isDone()) { if (LOG.isInfoEnabled()) { LOG.info(context.getName() + " finished Bucket refreshes"); } return false; } KUID lookupId = bucketIds.next(); future = context.lookup(lookupId); future.addDHTFutureListener(this); if (LOG.isInfoEnabled()) { LOG.info(context.getName() + " started a Bucket refresh lookup for " + lookupId); } return true; } public void handleFutureSuccess(FindNodeResult result) { if (LOG.isInfoEnabled()) { LOG.info(result); } if (!next()) { stop(); } } public void handleExecutionException(ExecutionException e) { LOG.error("ExecutionException", e); if (!next()) { stop(); } } public void handleCancellationException(CancellationException e) { LOG.debug("CancellationException", e); stop(); } public void handleInterruptedException(InterruptedException e) { LOG.debug("InterruptedException", e); stop(); } } }