/* * Copyright 2013 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.futures; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceArray; /** * The key future for recursive loops. A first version with the fork-join framework did not reduce the code complexity * significantly, thus I decided to write this class. The basic idea is that you can create parallel loops. For example * in a routing process (loop to find closest peers), one starts to ask 3 peers in parallel, the first that returns * result gets evaluated for new information about other peers, and a new peer is asked. If two peers finish, then two * other peers are asked. Thus, we keep always 3 connections running until we get the result. * * @author Thomas Bocek * @param <K> */ public class FutureForkJoin<K extends BaseFuture> extends BaseFutureImpl<FutureForkJoin<K>> implements BaseFuture { // the references are stored in an atomic array, as they are access (although sequentially) but in different // threads. private final AtomicReferenceArray<K> forks; private final int nrFutures; private final int nrFinishFuturesSuccess; private final boolean cancelFuturesOnFinish; private final List<K> forksCopy = new ArrayList<K>(); // all these values are accessed within synchronized blocks private int counter = 0; private int successCounter = 0; private final FutureDone<Void> futuresCompleted = new FutureDone<Void>(); private final AtomicInteger futureCounter; /** * Facade if we expect everything to return successfully. * * @param forks * The futures that can also be modified outside this class. If a future is finished the future in that * array will be set to null. A future may be initially null, which is considered a failure. */ public FutureForkJoin(final AtomicReferenceArray<K> forks) { this(forks.length(), false, forks); } /** * Create a future fork join object. * * @param nrFinishFuturesSuccess * Is the number of futures that we expect to succeed. * @param cancelFuturesOnFinish * Tells use if we should cancel the remaining futures. For get() it makes sense to cancel, for store() * it does not. * @param forks * The futures that can also be modified outside this class. If a future is finished the future in that * array will be set to null. A future may be initially null, which is considered a failure. */ public FutureForkJoin(final int nrFinishFuturesSuccess, final boolean cancelFuturesOnFinish, final AtomicReferenceArray<K> forks) { this.nrFinishFuturesSuccess = nrFinishFuturesSuccess; this.forks = forks; this.cancelFuturesOnFinish = cancelFuturesOnFinish; // the futures array may have null entries, so count first. nrFutures = forks.length(); futureCounter = new AtomicInteger(nrFutures); if (this.nrFutures <= 0) { failed("We have no futures: " + nrFutures); } else { join(); } self(this); } /** * Adds listeners and evaluates the result and when to notify the listeners. */ private void join() { for (int i = 0; i < nrFutures; i++) { synchronized (lock) { if (completed) { return; } } final int index = i; if (forks.get(index) != null) { forks.get(index).addListener(new BaseFutureAdapter<K>() { @Override public void operationComplete(final K future) throws Exception { evaluate(future, index); if (futureCounter.decrementAndGet() == 0) { futuresCompleted.done(); } } }); } else { boolean notifyNow = false; synchronized (lock) { // if counter reaches nrFutures, that means we are finished // and in this case, we failed otherwise, in evaluate, // successCounter would finish first if (++counter >= nrFutures) { notifyNow = finish(FutureType.FAILED); } } if (futureCounter.decrementAndGet() == 0) { futuresCompleted.done(); } if (notifyNow) { notifyListeners(); cancelAll(); return; } } } } /** * Evaluates one future and determines if this future is finished. * * @param finished * The future to evaluate * @param index * the index in the array. */ private void evaluate(final K finished, final int index) { boolean notifyNow = false; synchronized (lock) { // this if statement is very important. If the future is finished, then any subsequent evaluation, which // will happen as we add the listener in the join, must not set the future to null! if (completed) { return; } // add the future that we have evaluated forksCopy.add(finished); forks.set(index, (K) null); if (finished.isSuccess() && ++successCounter >= nrFinishFuturesSuccess) { notifyNow = finish(FutureType.OK); } else if (++counter >= nrFutures) { notifyNow = finish(FutureType.FAILED); } } if (notifyNow) { notifyListeners(); cancelAll(); } } /** * Cancels all remaining futures if requested by the user. */ private void cancelAll() { if (cancelFuturesOnFinish) { for (int i = 0; i < nrFutures; i++) { K future = forks.get(i); if (future != null) { future.cancel(); } } } } /** * Sets this future to complete. Always call this from a synchronized block. * * @param type * The type of the future, if it has failed, its ok, or it has been canceled * @return True if other listener should get notified */ private boolean finish(final FutureType type) { if (!completedAndNotify()) { return false; } this.type = type; this.reason = reason(); return true; } private String reason() { final StringBuilder sb = new StringBuilder("forkjoin:"); sb.append(type.name()); for (final K k : completed()) { sb.append(",").append(k.failedReason()); } return sb.toString(); } /** * Returns the last evaluated future. This method may return null if an array with null values have been has been * used. * * @return The last evaluated future. */ public K last() { synchronized (lock) { if(!forksCopy.isEmpty()) { return forksCopy.get(forksCopy.size() - 1); } } return null; } public K first() { synchronized (lock) { if(!forksCopy.isEmpty()) { return forksCopy.get(0); } } return null; } /** * Returns a list of evaluated futures. The last completed future is the same as retrieved with {@link #last()}. * * @return A list of evaluated futures. */ public List<K> completed() { synchronized (lock) { return forksCopy; } } /** * Returns the number of successful finished futures. * * @return The number of successful finished futures */ public int successCounter() { synchronized (lock) { return successCounter; } } public FutureDone<Void> futuresCompleted() { synchronized (lock) { return futuresCompleted; } } }