package water; import java.util.Arrays; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import water.util.Log; /** A collection of Futures that can be extended, or blocked on the whole * collection. Undefined if you try to add Futures while blocking. * <p> * Used as a service to sub-tasks, collect pending-but-not-yet-done future * tasks that need to complete prior to *this* task completing... or if the * caller of this task is knowledgeable, pass these pending tasks along to him * to block on before he completes. * <p> * Highly efficient under a high load of short-completion-time Futures. Safe * to call with e.g. millions of Futures per second, as long as they all * complete in roughly the same rate. */ public class Futures { // implemented as an exposed array mostly because ArrayList doesn't offer // synchronization and constant-time removal. Future[] _pending = new Future[1]; int _pending_cnt; private Throwable _ex; private void waitAndCheckForException(Future f) { try { f.get(); } catch(CancellationException ex){ // ignore cancelled tasks } catch(Throwable t) { if(_ex == null) _ex = t instanceof ExecutionException?t.getCause():t; } } /** Some Future task which needs to complete before this Futures completes */ synchronized public Futures add( Future f ) { if( f == null ) return this; if(f.isDone()) { waitAndCheckForException(f); return this; } // NPE here if this Futures has already been added to some other Futures // list, and should be added to again. if( _pending_cnt == _pending.length ) { cleanCompleted(); if( _pending_cnt == _pending.length ) _pending = Arrays.copyOf(_pending,_pending_cnt<<1); } _pending[_pending_cnt++] = f; return this; } /** Clean out from the list any pending-tasks which are already done. Note * that this drops the algorithm from O(n) to O(1) in practice, since mostly * things clean out as fast as new ones are added and the list never gets * very large. */ synchronized private void cleanCompleted(){ for( int i=0; i<_pending_cnt; i++ ) if( _pending[i].isDone() ) {// Done? waitAndCheckForException(_pending[i]); // Do cheap array compression to remove from list _pending[i--] = _pending[--_pending_cnt]; } } /** Merge pending-task lists (often as part of doing a 'reduce' step) */ public void add( Futures fs ) { if( fs == null ) return; assert fs != this; // No recursive death, please for( int i=0; i<fs._pending_cnt; i++ ) add(fs._pending[i]); // NPE here if using a dead Future fs._pending = null; // You are dead, should never be inserted into again } /** Block until all pending futures have completed or canceled. */ public final void blockForPending() { // Block until the last Future finishes. while (true) { Future f; synchronized (this) { if (_pending_cnt == 0) break; f = _pending[--_pending_cnt]; } waitAndCheckForException(f); } if (_ex != null) throw new RuntimeException(_ex); } }