package net.tomp2p.p2p; import java.util.Collection; import java.util.Map; import java.util.NavigableSet; import java.util.Random; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.tomp2p.futures.BaseFuture; import net.tomp2p.futures.FutureResponse; import net.tomp2p.futures.FutureRouting; import net.tomp2p.p2p.builder.RoutingBuilder; import net.tomp2p.peers.PeerAddress; import net.tomp2p.rpc.DigestInfo; import net.tomp2p.utils.Utils; /** * The routing mechanism. Since this is called from Netty handlers, we don't have any visibility issues. If you want to * use an execution handler, then it must be an OrderedMemoryAwareThreadPoolExecutor. * * http://stackoverflow.com/questions/8304309/keeping-state-in-a-netty-channelhandler * * @author Thomas Bocek * */ public class RoutingMechanism { private static final Logger LOG = LoggerFactory.getLogger(RoutingMechanism.class); private final AtomicReferenceArray<FutureResponse> futureResponses; private final FutureRouting futureRoutingResponse; private NavigableSet<PeerAddress> queueToAsk; private SortedSet<PeerAddress> alreadyAsked; private SortedMap<PeerAddress, DigestInfo> directHits; private NavigableSet<PeerAddress> potentialHits; private int nrNoNewInfo = 0; private int nrFailures = 0; private int nrSuccess = 0; private int maxDirectHits; private int maxNoNewInfo; private int maxFailures; private int maxSucess; private boolean stopCreatingNewFutures; /** * Creates the routing mechanism. Make sure to set the max* fields. * * @param futureResponses * The current future responeses that are running * @param futureRoutingResponse * The reponse future from this routing request */ public RoutingMechanism(final AtomicReferenceArray<FutureResponse> futureResponses, final FutureRouting futureRoutingResponse) { this.futureResponses = futureResponses; this.futureRoutingResponse = futureRoutingResponse; } /** * @return The number of parallel requests. The number is determined by the length of the future response array */ public int parallel() { return futureResponses.length(); } /** * @return True if we should stop creating more futures, false otherwise */ public boolean isStopCreatingNewFutures() { return stopCreatingNewFutures; } /** * @param i * The number of the future response to get * @return The future response at position i */ public FutureResponse futureResponse(final int i) { return futureResponses.get(i); } /** * @param i * The number of the future response to get and set * @param futureResponse * The future response to set * @return The old future response */ public FutureResponse futureResponse(final int i, final FutureResponse futureResponse) { return futureResponses.getAndSet(i, futureResponse); } /** * @param queueToAsk * The queue that contains the peers that will be queried in the future * @return This class */ public RoutingMechanism queueToAsk(final NavigableSet<PeerAddress> queueToAsk) { this.queueToAsk = queueToAsk; return this; } /** * @return The queue that contains the peers that will be queried in the future */ public NavigableSet<PeerAddress> queueToAsk() { synchronized (this) { return queueToAsk; } } /** * @param alreadyAsked * The peer we have already queried, we need to store them to not ask the same peers again * @return This class */ public RoutingMechanism alreadyAsked(final SortedSet<PeerAddress> alreadyAsked) { this.alreadyAsked = alreadyAsked; return this; } /** * @return The peer we have already queried, we need to store them to not ask the same peers again */ public SortedSet<PeerAddress> alreadyAsked() { synchronized (this) { return alreadyAsked; } } /** * @param potentialHits * The potential hits are those reported by other peers that we did not check if they contain certain * data. * @return This class */ public RoutingMechanism potentialHits(final NavigableSet<PeerAddress> potentialHits) { this.potentialHits = potentialHits; return this; } /** * @return The potential hits are those reported by other peers that we did not check if they contain certain data. */ public NavigableSet<PeerAddress> potentialHits() { synchronized (this) { return potentialHits; } } /** * @param directHits * The peers that have certain data stored on it * @return This class */ public RoutingMechanism directHits(final SortedMap<PeerAddress, DigestInfo> directHits) { this.directHits = directHits; return this; } /** * @return The peers that have certain data stored on it */ public SortedMap<PeerAddress, DigestInfo> directHits() { return directHits; } public int getMaxDirectHits() { return maxDirectHits; } public void setMaxDirectHits(int maxDirectHits) { this.maxDirectHits = maxDirectHits; } public int getMaxNoNewInfo() { return maxNoNewInfo; } public void setMaxNoNewInfo(int maxNoNewInfo) { this.maxNoNewInfo = maxNoNewInfo; } public int getMaxFailures() { return maxFailures; } public void setMaxFailures(int maxFailures) { this.maxFailures = maxFailures; } public int getMaxSucess() { return maxSucess; } public void setMaxSucess(int maxSucess) { this.maxSucess = maxSucess; } public PeerAddress pollFirstInQueueToAsk() { synchronized (this) { return queueToAsk.pollFirst(); } } public PeerAddress pollRandomInQueueToAsk(Random rnd) { synchronized (this) { return Utils.pollRandom(queueToAsk(), rnd); } } public void addToAlreadyAsked(PeerAddress next) { synchronized (this) { alreadyAsked.add(next); } } public void setNeighbors(RoutingBuilder builder) { synchronized (this) { futureRoutingResponse.setNeighbors(directHits, potentialHits, alreadyAsked, builder.isBootstrap(), builder.isRoutingToOthers()); } } /** * Cancel the future that causes the underlying futures to cancel as well. */ public void cancel() { int len = futureResponses.length(); for (int i = 0; i < len; i++) { BaseFuture baseFuture = futureResponses.get(i); if (baseFuture != null) { baseFuture.cancel(); } } } public AtomicReferenceArray<FutureResponse> futureResponses() { return futureResponses; } public void addPotentialHits(PeerAddress remotePeer) { synchronized (this) { potentialHits.add(remotePeer); } } public void stopCreatingNewFutures(boolean stopCreatingNewFutures) { this.stopCreatingNewFutures = stopCreatingNewFutures; } public boolean evaluateFailed() { return (++nrFailures) > getMaxFailures(); } public boolean evaluateSuccess(PeerAddress remotePeer, DigestInfo digestBean, Collection<PeerAddress> newNeighbors, boolean last) { boolean finished; synchronized (this) { if (evaluateDirectHits(remotePeer, directHits, digestBean, getMaxDirectHits())) { // stop immediately LOG.debug("we have enough direct hits {}", directHits); finished = true; stopCreatingNewFutures = true; } else if ((++nrSuccess) > getMaxSucess()) { // wait until pending futures are finished LOG.debug("we have reached max success {}", nrSuccess); finished = last; stopCreatingNewFutures = true; } else if (evaluateInformation(newNeighbors, queueToAsk, alreadyAsked, getMaxNoNewInfo())) { // wait until pending futures are finished LOG.debug("we have no new information for the {} time", getMaxNoNewInfo()); finished = last; stopCreatingNewFutures = true; } else { // continue finished = false; stopCreatingNewFutures = false; } } return finished; } /** * For get requests we can finish earlier if we found the data we were looking for. This checks if we reached the * end of our search. * * @param remotePeer * The remote peer that gave us thi digest information * @param directHits * The result map that will store how many peers reported that data is there * @param digestBean * The digest information coming from the remote peer * @param maxDirectHits * The max. number of direct hits, e.g. finding the value we were looking for before we can stop. * @return True if we can stop, false, if we should continue. */ static boolean evaluateDirectHits(final PeerAddress remotePeer, final Map<PeerAddress, DigestInfo> directHits, final DigestInfo digestBean, final int maxDirectHits) { if (digestBean.getSize() > 0) { directHits.put(remotePeer, digestBean); if (directHits.size() >= maxDirectHits) { return true; } } return false; } /** * Checks if we reached the end of our search. * * @param newNeighbors * The new neighbors we just received * @param queueToAsk * The peers that are in the queue to be asked * @param alreadyAsked * The peers we have already asked * @param noNewInfo * counter how many times we did not find any peer that is closer to the target * @param maxNoNewInfo * The maximum number of replies from neighbors that do not give us closer peers * @return True if we should stop, false if we should continue with the routing */ boolean evaluateInformation(final Collection<PeerAddress> newNeighbors, final SortedSet<PeerAddress> queueToAsk, final Set<PeerAddress> alreadyAsked, final int maxNoNewInfo) { boolean newInformation = merge(queueToAsk, newNeighbors, alreadyAsked); if (newInformation) { nrNoNewInfo = 0; return false; } else { return (++nrNoNewInfo) >= maxNoNewInfo; } } /** * Updates queueToAsk with new data, returns if we found peers closer than we already know. * * @param queueToAsk * The queue to get updated * @param newPeers * The new peers reported from remote peers. Since the remote peers do not know what we know, we need to * filter this information. * @param alreadyAsked * The peers we already know. * @return True if we added peers that are closer to the target than we already knew. Please note, it will return * false if we add new peers that are not closer to a target. */ static boolean merge(final SortedSet<PeerAddress> queueToAsk, final Collection<PeerAddress> newPeers, final Collection<PeerAddress> alreadyAsked) { final SortedSet<PeerAddress> result = new TreeSet<PeerAddress>(queueToAsk.comparator()); Utils.difference(newPeers, result, alreadyAsked); if (result.size() == 0) { return false; } PeerAddress first = result.first(); boolean newInfo = isNew(queueToAsk, first); queueToAsk.addAll(result); return newInfo; } /** * Checks if an item will be the highest in a sorted set. * * @param queueToAsk * The sorted set to check * @param item * The element to check if it will be the highest in the sorted set * @return True, if item will be the highest element. */ private static boolean isNew(final SortedSet<PeerAddress> queueToAsk, final PeerAddress item) { if (queueToAsk.contains(item)) { return false; } SortedSet<PeerAddress> tmp = queueToAsk.headSet(item); return tmp.size() == 0; } public int nrNoNewInfo() { return nrNoNewInfo; } }