/*
* Copyright 2009 Thomas Bocek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package net.tomp2p.p2p;
import java.util.Collection;
import java.util.Comparator;
import java.util.NavigableSet;
import java.util.Random;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import net.tomp2p.connection2.ChannelCreator;
import net.tomp2p.connection2.PeerBean;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.FutureForkJoin;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.futures.FutureRouting;
import net.tomp2p.futures.FutureWrapper;
import net.tomp2p.message.Message2;
import net.tomp2p.message.Message2.Type;
import net.tomp2p.p2p.builder.RoutingBuilder;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerMap;
import net.tomp2p.rpc.DigestInfo;
import net.tomp2p.rpc.NeighborRPC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles routing of nodes to other nodes.
*
* TODO: add timing constraints for the routing. This would allow for slow routing requests to have a chance to report
* the neighbors.
*
* @author Thomas Bocek
*/
public class DistributedRouting {
private static final Logger LOG = LoggerFactory.getLogger(DistributedRouting.class);
private final NeighborRPC neighbors;
private final PeerBean peerBean;
private final Random rnd;
/**
* The routing process involves multiple RPCs, mostly UDP based.
*
* @param peerBean
* The peer bean
* @param neighbors
* The neighbor RPC that will be issues
*/
public DistributedRouting(final PeerBean peerBean, final NeighborRPC neighbors) {
this.neighbors = neighbors;
this.peerBean = peerBean;
// stable random number. No need to be truly random
rnd = new Random(peerBean.serverPeerAddress().getPeerId().hashCode());
}
/**
* Bootstraps to the given peerAddresses, i.e. looking for near nodes
*
* @param peerAddresses
* the node to which bootstrap should be performed to
* @param routingBuilder
* All relevant information for the routing process
* @param cc
* The channel creator
* @return a FutureRouting object, is set to complete if the route has been found
*/
public FutureWrapper<FutureRouting> bootstrap(final Collection<PeerAddress> peerAddresses,
final RoutingBuilder routingBuilder, final ChannelCreator cc) {
// search close peers
LOG.debug("broadcast to {}", peerAddresses);
// first we find close peers to us
routingBuilder.setBootstrap(true);
final FutureRouting futureRouting = routing(peerAddresses, routingBuilder, Type.REQUEST_1, cc);
final FutureWrapper<FutureRouting> futureWrapper = new FutureWrapper<FutureRouting>();
// to not become a Fachidiot (expert idiot), we need to know other peers
// as well. This is important if this peer is passive and only replies on requests from other peers
futureRouting.addListener(new BaseFutureAdapter<FutureRouting>() {
@Override
public void operationComplete(final FutureRouting future) throws Exception {
// setting this to null causes to search for a random number
routingBuilder.setLocationKey(null);
FutureRouting futureRouting = routing(peerAddresses, routingBuilder, Type.REQUEST_1, cc);
futureWrapper.waitFor(futureRouting);
}
});
return futureWrapper;
}
/**
* Looks for a route to the given locationKey.
*
* @param routingBuilder
* All relevant information for the routing process
* @param type
* The type of the routing, there can at most four types
* @param cc
* The channel creator
*
* @return a FutureRouting object, is set to complete if the route has been found
*/
public FutureRouting route(final RoutingBuilder routingBuilder, final Type type, final ChannelCreator cc) {
// for bad distribution, use large NO_NEW_INFORMATION
Collection<PeerAddress> startPeers = peerBean.peerMap().closePeers(routingBuilder.getLocationKey(),
routingBuilder.getParallel() * 2);
return routing(startPeers, routingBuilder, type, cc);
}
/**
* Looks for a route to the given locationKey.
*
* @param peerAddresses
* nodes which should be asked first for a route
* @param locationKey
* the node a route should be found to
* @param domainKey
* the domain of the network the current node and locationKey is in
* @param contentKeys
* nodes which we got from another node
* @param maxDirectHits
* number of direct hits to stop at
* @param maxNoNewInfo
* number of nodes asked without new information to stop at
* @param maxFailures
* number of failures to stop at
* @param parallel
* number of routing requests performed concurrently
* @return a FutureRouting object, is set to complete if the route has been found
*/
private FutureRouting routing(final Collection<PeerAddress> peerAddresses,
final RoutingBuilder routingBuilder, final Type type, final ChannelCreator cc) {
if (peerAddresses == null) {
throw new IllegalArgumentException("you need to specify some nodes");
}
boolean randomSearch = routingBuilder.getLocationKey() == null;
final FutureRouting futureRouting = new FutureRouting();
//
final Comparator<PeerAddress> comparator;
if (randomSearch) {
comparator = peerBean.peerMap().createComparator();
} else {
comparator = PeerMap.createComparator(routingBuilder.getLocationKey());
}
final NavigableSet<PeerAddress> queueToAsk = new TreeSet<PeerAddress>(comparator);
// we can reuse the comparator
final SortedSet<PeerAddress> alreadyAsked = new TreeSet<PeerAddress>(comparator);
// as presented by Kazuyuki Shudo at AIMS 2009, its better to ask random
// peers with the data than ask peers that ar ordered by distance ->
// this balances load.
final SortedMap<PeerAddress, DigestInfo> directHits = new TreeMap<PeerAddress, DigestInfo>(peerBean
.peerMap().createComparator());
final NavigableSet<PeerAddress> potentialHits = new TreeSet<PeerAddress>(comparator);
// fill initially
queueToAsk.addAll(peerAddresses);
alreadyAsked.add(peerBean.serverPeerAddress());
potentialHits.add(peerBean.serverPeerAddress());
// domainkey can be null if we bootstrap
if (type == Type.REQUEST_2 && routingBuilder.getDomainKey() != null && !randomSearch) {
DigestInfo digestBean = peerBean.storage().digest(routingBuilder.getLocationKey(),
routingBuilder.getDomainKey(), routingBuilder.getContentKey());
if (digestBean.getSize() > 0) {
directHits.put(peerBean.serverPeerAddress(), digestBean);
}
} else if (type == Type.REQUEST_3 && !randomSearch) {
DigestInfo digestInfo = peerBean.trackerStorage().digest(routingBuilder.getLocationKey(),
routingBuilder.getDomainKey(), routingBuilder.getContentKey());
// we always put ourselfs to the tracker list, so we need to check
// if we know also other peers on our trackers.
if (digestInfo.getSize() > 0) {
directHits.put(peerBean.serverPeerAddress(), digestInfo);
}
}
// with request4 we should never see random search, but just to be very
// specific here add the flag
// TODO: no task manager in the core
// else if (type == Type.REQUEST_4 && !randomSearch) {
// DigestInfo digestInfo = peerBean.taskManager().digest();
// if (digestInfo.getSize() > 0) {
// directHits.put(peerBean.serverPeerAddress(), digestInfo);
// }
// }
// peerAddresses is typically only 0 for routing. However, the user may
// bootstrap with an empty List<PeerAddress>, which will then also be 0.
if (peerAddresses.size() == 0) {
futureRouting.setNeighbors(directHits, potentialHits, alreadyAsked, routingBuilder.isBootstrap(),
false);
} else {
// if a peer bootstraps to itself, then the size of peerAddresses
// is 1 and it contains itself. Check for that because we need to
// know if we are routing, bootstrapping and bootstrapping to
// ourselfs, to return the correct status for the future
boolean isRoutingOnlyToSelf = (peerAddresses.size() == 1 && peerAddresses.iterator().next()
.equals(peerBean.serverPeerAddress()));
RoutingMechanism routingMechanism = routingBuilder.createRoutingMechanism(futureRouting);
routingMechanism.queueToAsk(queueToAsk);
routingMechanism.potentialHits(potentialHits);
routingMechanism.directHits(directHits);
routingMechanism.alreadyAsked(alreadyAsked);
routingBuilder.routingOnlyToSelf(isRoutingOnlyToSelf);
routingRec(routingBuilder, routingMechanism, type, cc);
}
return futureRouting;
}
/**
* Looks for a route to the given locationKey, performing recursively. Since this method is not called concurrently,
* but sequentially, no synchronization is necessary.
*
* @param futureResponses
* expected responses
* @param futureRouting
* the current routing future used
* @param queueToAsk
* all nodes which should be asked for routing information
* @param alreadyAsked
* nodes which already have been asked
* @param directHits
* stores direct hits received
* @param nrNoNewInfo
* number of nodes contacted without any new information
* @param nrFailures
* number of nodes without a response
* @param nrSucess
* number of peers that responded
* @param maxDirectHits
* number of direct hits to stop at
* @param maxNoNewInfo
* number of nodes asked without new information to stop at
* @param maxFailures
* number of failures to stop at
* @param maxSuccess
* number of successful requests. To avoid looping if every peer gives a new piece of information.
* @param parallel
* number of routing requests performed concurrently
* @param locationKey
* the node a route should be found to
* @param domainKey
* the domain of the network the current node and locationKey is in
* @param contentKeys
* nodes which we got from another node
*/
private void routingRec(final RoutingBuilder routingBuilder, final RoutingMechanism routingMechanism,
final Type type, final ChannelCreator channelCreator) {
final boolean randomSearch = routingBuilder.getLocationKey() == null;
int active = 0;
for (int i = 0; i < routingMechanism.parallel(); i++) {
if (routingMechanism.futureResponse(i) == null && !routingMechanism.isStopCreatingNewFutures()) {
final PeerAddress next;
if (randomSearch) {
next = routingMechanism.pollRandomInQueueToAsk(rnd);
} else {
next = routingMechanism.pollFirstInQueueToAsk();
}
if (next != null) {
routingMechanism.addToAlreadyAsked(next);
active++;
// if we search for a random peer, then the peer should
// return the address farest away.
final Number160 locationKey2 = randomSearch ? next.getPeerId().xor(Number160.MAX_VALUE)
: routingBuilder.getLocationKey();
routingBuilder.setLocationKey(locationKey2);
routingMechanism.futureResponse(i, neighbors.closeNeighbors(next,
routingBuilder.searchValues(), type, channelCreator, routingBuilder));
LOG.debug("get close neighbors: {} on {}", next, i);
}
} else if (routingMechanism.futureResponse(i) != null) {
LOG.debug("activity on {}", i);
active++;
}
}
if (active == 0) {
LOG.debug("no activity, closing");
routingMechanism.setNeighbors(routingBuilder);
routingMechanism.cancel();
return;
}
final boolean last = active == 1;
final FutureForkJoin<FutureResponse> fp = new FutureForkJoin<FutureResponse>(1, false,
routingMechanism.futureResponses());
fp.addListener(new BaseFutureAdapter<FutureForkJoin<FutureResponse>>() {
@Override
public void operationComplete(final FutureForkJoin<FutureResponse> future) throws Exception {
final boolean finished;
if (future.isSuccess()) {
Message2 lastResponse = future.getLast().getResponse();
PeerAddress remotePeer = lastResponse.getSender();
routingMechanism.addPotentialHits(remotePeer);
Collection<PeerAddress> newNeighbors = lastResponse.getNeighborsSet(0).neighbors();
Integer resultSize = lastResponse.getInteger(0);
Number160 keyDigest = lastResponse.getKey(0);
Number160 contentDigest = lastResponse.getKey(1);
DigestInfo digestBean = new DigestInfo(keyDigest, contentDigest, resultSize == null? 0 : resultSize);
LOG.debug("Peer ({}) {} reported {}", (digestBean.getSize() > 0 ? "direct" : "none"), remotePeer,
newNeighbors);
finished = routingMechanism.evaluateSuccess(remotePeer, digestBean, newNeighbors, last);
LOG.debug("Routing finished {} / {}", finished,
routingMechanism.isStopCreatingNewFutures());
} else {
// if it failed but the failed is the closest one, its good to try again, since the peer might just
// be busy
LOG.warn("routing error {}", future.getFailedReason());
finished = routingMechanism.evaluateFailed();
routingMechanism.stopCreatingNewFutures(finished);
}
if (finished) {
LOG.debug("finished routing, direct hits: {} potential: {}",
routingMechanism.directHits(), routingMechanism.potentialHits());
routingMechanism.setNeighbors(routingBuilder);
routingMechanism.cancel();
// stop all operations, as we are finished, no need to go further
} else {
routingRec(routingBuilder, routingMechanism, type, channelCreator);
}
}
});
}
/**
* Cancel the future that causes the underlying futures to cancel as well.
*
* @param futureResponses
* The array with the future responses some items may be null
*/
/*public static void cancel(final AtomicReferenceArray<? extends BaseFuture> futureResponses) {
int len = futureResponses.length();
for (int i = 0; i < len; i++) {
BaseFuture baseFuture = futureResponses.get(i);
if (baseFuture != null) {
baseFuture.cancel();
}
}
}*/
}