/* * Copyright 2012 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.Map; import java.util.NavigableSet; import java.util.Set; import java.util.SortedMap; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceArray; import net.tomp2p.connection2.ChannelCreator; import net.tomp2p.connection2.ConnectionReservation; import net.tomp2p.futures.BaseFutureAdapter; import net.tomp2p.futures.FutureAsyncTask; import net.tomp2p.futures.FutureChannelCreator; import net.tomp2p.futures.FutureForkJoin; import net.tomp2p.futures.FutureRouting; import net.tomp2p.futures.FutureTask; import net.tomp2p.message.Message.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.storage.Data; import net.tomp2p.task.AsyncTask; import net.tomp2p.task.Worker; import net.tomp2p.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DistributedTask { final private static Logger logger = LoggerFactory.getLogger(DistributedTask.class); final private DistributedRouting routing; final private AsyncTask asyncTask; public DistributedTask(DistributedRouting routing, AsyncTask asyncTask) { this.routing = routing; this.asyncTask = asyncTask; } /** * Submit a task to the DHT. The node that is close to the locationKey will get the task. The routing process * returns a list of close peers with the current load. The peer with the lowest load will get the task. * * @param locationKey * @param dataMap * @param routingConfiguration * @param taskConfiguration * @param futureChannelCreator * @param signMessage * @param isAutomaticCleanup * @param connectionReservation * @return */ public FutureTask submit(final Number160 locationKey, final Map<Number160, Data> dataMap, final Worker worker, final RoutingConfiguration routingConfiguration, final RequestP2PConfiguration requestP2PConfiguration, final FutureChannelCreator futureChannelCreator, final boolean signMessage, final boolean isManualCleanup, final ConnectionReservation connectionReservation) { final FutureTask futureTask = new FutureTask(); futureChannelCreator.addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(FutureChannelCreator future) throws Exception { if (future.isSuccess()) { final ChannelCreator channelCreator = future.getChannelCreator(); // routing, find close peers final FutureRouting futureRouting = createRouting(locationKey, null, null, routingConfiguration, requestP2PConfiguration, Type.REQUEST_4, channelCreator); futureRouting.addListener(new BaseFutureAdapter<FutureRouting>() { @Override public void operationComplete(FutureRouting future) throws Exception { if (futureRouting.isSuccess()) { // direct hits mean that there // is a task scheduled SortedMap<PeerAddress, DigestInfo> map = future.getDirectHitsDigest(); // potential hits means that no // task is scheduled NavigableSet<Pair> queue = findBest(map, future.getPotentialHits(), locationKey); parallelRequests(futureTask, queue, requestP2PConfiguration, channelCreator, locationKey, dataMap, worker, requestP2PConfiguration.isForceUPD(), signMessage); } else { futureTask.setFailed(futureRouting); } } }); } if (!isManualCleanup) { Utils.addReleaseListenerAll(futureTask, connectionReservation, future.getChannelCreator()); } else { futureTask.setFailed(future); } } }); return futureTask; } private void parallelRequests(FutureTask futureTask, NavigableSet<Pair> queue, RequestP2PConfiguration requestP2PConfiguration, ChannelCreator channelCreator, Number160 taskId, Map<Number160, Data> dataMap, Worker worker, boolean forceUDP, boolean sign) { FutureAsyncTask[] futures = new FutureAsyncTask[requestP2PConfiguration.getParallel()]; loopRec(queue, requestP2PConfiguration.getMinimumResults(), new AtomicInteger(0), requestP2PConfiguration.getMaxFailure(), requestP2PConfiguration.getParallelDiff(), new AtomicReferenceArray<FutureAsyncTask>(futures), futureTask, true, channelCreator, taskId, dataMap, worker, forceUDP, sign); } private void loopRec(final NavigableSet<Pair> queue, final int min, final AtomicInteger nrFailure, final int maxFailure, final int parallelDiff, final AtomicReferenceArray<FutureAsyncTask> futures, final FutureTask futureTask, final boolean cancelOnFinish, final ChannelCreator channelCreator, final Number160 taskId, final Map<Number160, Data> dataMap, final Worker mapper, final boolean forceUDP, final boolean sign) { int active = 0; for (int i = 0; i < min + parallelDiff; i++) { if (futures.get(i) == null) { PeerAddress next = queue.pollFirst().peerAddress; if (next != null) { active++; FutureAsyncTask futureAsyncTask = asyncTask.submit(next, channelCreator, taskId, dataMap, mapper, forceUDP, sign); futures.set(i, futureAsyncTask); futureTask.addRequests(futureAsyncTask); } } else { active++; } } if (active == 0) { futureTask.setDone(); DistributedRouting.cancel(cancelOnFinish, min + parallelDiff, futures); return; } if (logger.isDebugEnabled()) { logger.debug("fork/join status: " + min + "/" + active + " (" + parallelDiff + ")"); } FutureForkJoin<FutureAsyncTask> fp = new FutureForkJoin<FutureAsyncTask>(Math.min(min, active), false, futures); fp.addListener(new BaseFutureAdapter<FutureForkJoin<FutureAsyncTask>>() { @Override public void operationComplete(FutureForkJoin<FutureAsyncTask> future) throws Exception { for (FutureAsyncTask futureAsyncTask : future.getCompleted()) { futureTask.setProgress(futureAsyncTask); } // we are finished if forkjoin says so or we got too many // failures if (future.isSuccess() || nrFailure.incrementAndGet() > maxFailure) { if (cancelOnFinish) { DistributedRouting.cancel(cancelOnFinish, min + parallelDiff, futures); } futureTask.setDone(); } else { loopRec(queue, min - future.getSuccessCounter(), nrFailure, maxFailure, parallelDiff, futures, futureTask, cancelOnFinish, channelCreator, taskId, dataMap, mapper, forceUDP, sign); } } }); } private FutureRouting createRouting(Number160 locationKey, Number160 domainKey, Set<Number160> contentKeys, RoutingConfiguration routingConfiguration, RequestP2PConfiguration requestP2PConfiguration, Type type, ChannelCreator channelCreator) { return routing.route(new RoutingBuilder(locationKey, domainKey, contentKeys, routingConfiguration.getDirectHits(), routingConfiguration.getMaxNoNewInfo(requestP2PConfiguration.getMinimumResults()), routingConfiguration.getMaxFailures(), routingConfiguration.getMaxSuccess(), routingConfiguration.getParallel(), routingConfiguration.isForceTCP()), type, channelCreator); } static NavigableSet<Pair> findBest(SortedMap<PeerAddress, DigestInfo> map, NavigableSet<PeerAddress> navigableSet, Number160 locationKey) { NavigableSet<Pair> set = new TreeSet<DistributedTask.Pair>(); for (Map.Entry<PeerAddress, DigestInfo> entry : map.entrySet()) { set.add(new Pair(entry.getKey(), entry.getValue().getSize(), locationKey)); } for (PeerAddress peerAddress : navigableSet) { set.add(new Pair(peerAddress, 0, locationKey)); } return set; } private static class Pair implements Comparable<Pair> { private final PeerAddress peerAddress; private final int queueSize; private final Number160 locationKey; public Pair(PeerAddress peerAddress, int queueSize, Number160 locationKey) { this.peerAddress = peerAddress; this.queueSize = queueSize; this.locationKey = locationKey; } @Override public int compareTo(Pair o) { int diff = queueSize - o.queueSize; if (diff != 0) return diff; return PeerMap.isKadCloser(locationKey, peerAddress, o.peerAddress); } @Override public boolean equals(Object obj) { if (!(obj instanceof Pair)) return false; return compareTo((Pair) obj) == 0; } } }