/* This file is part of Reactive Cascade which is released under The MIT License. See license.md , https://github.com/futurice/cascade and http://reactivecascade.com for details. This is open source for the common good. Please contribute improvements by pull request or contact paulirotta@gmail.com */ package com.reactivecascade.util; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.reactivecascade.i.IAltFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static com.reactivecascade.Async.currentThreadType; /** * A {@link java.util.concurrent.Future} which can be used to safely wait for the results * from an {@link IAltFuture}. * <p> * Normally we don't like to hold one thread waiting for the result of another thread. Doing this * on a routine basis causes lots of context switching and can backlog into either many threads * or deadlock because a limited number of threads are all in use. * <p> * This can be useful for example to externally and synchronously test the results of an * asynchronous process. There is no risk because the situation is tightly controlled and * requires performance be secondary to rigid conformance. * * @param <IN> the upchain type * @param <OUT> the downchain type */ public class AltFutureFuture<IN, OUT> extends Origin implements Future<OUT> { private static final long DEFAULT_GET_TIMEOUT = 5000; private static final long CHECK_INTERVAL = 50; // This is a fallback in case you for example have an error and fail to altFuture.notifyAll() when finished @NonNull private final IAltFuture<IN, OUT> altFuture; private final Object mutex = new Object(); /** * Create a new {@link Future} which wraps an {@link IAltFuture} to allow use of blocking * operations. * <p> * Note that generally we do not wish to use this except for special circumstances such as synchronizing * the system test thread with the items being tested. They may exist other special cases, but most * often you can re-factor your code as a pure non-blocking chain instead of using this class. * * @param altFuture to be wrapped for traditional blocking access */ public AltFutureFuture(@NonNull final IAltFuture<IN, OUT> altFuture) { this.altFuture = altFuture; } @Override // Future public boolean cancel(boolean mayInterruptIfRunning) { return altFuture.cancel("DoneFuture was cancelled"); } @Override // Future public boolean isCancelled() { return altFuture.isCancelled(); } @Override // Future public boolean isDone() { return altFuture.isDone(); } @Override // Future @Nullable public OUT get() { try { return get(DEFAULT_GET_TIMEOUT, TimeUnit.MILLISECONDS); } catch (Exception e) { RCLog.throwRuntimeException(this, "Timeout waiting for RunnableAltFuture to complete. Did you remember to .fork()?, new RuntimeException", e); } return null; } /** * Verify that calls to wait for this {@link Future} such as {@link #get(long, TimeUnit)} will not * deadlock due to single-threaded access on the same thread as the item which might block. */ public void assertThreadSafe() { if (altFuture.getThreadType() == currentThreadType() && altFuture.getThreadType().isInOrderExecutor()) { throw new UnsupportedOperationException("Do not run your tests from the same single-threaded IThreadType as the threads you are testing: " + altFuture.getThreadType()); } } /** * Block the current thread until the associated IAltFuture completes or errors out * * @param timeout max time to wait for the RunnableAltFuture to complete * @param unit timeout units * @return null if there was an exception during execution * @throws InterruptedException * @throws ExecutionException * @throws TimeoutException */ @Override // Future @Nullable public OUT get(long timeout, @NonNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { if (!isDone()) { assertThreadSafe(); } final long t = System.currentTimeMillis(); final long endTime = t + unit.toMillis(timeout); if (!isDone()) { final IAltFuture<OUT, OUT> iaf = altFuture.then(() -> { // Attach this to speed up and notify to continue the Future when the RunnableAltFuture finishes // For speed, we don't normally notify after RunnableAltFuture end synchronized (mutex) { mutex.notifyAll(); } }); } while (!isDone()) { if (System.currentTimeMillis() >= endTime) { RCLog.throwTimeoutException(this, "Waited " + (System.currentTimeMillis() - t) + "ms for RunnableAltFuture to end: " + altFuture); } synchronized (mutex) { final long t2 = Math.min(CHECK_INTERVAL, endTime - System.currentTimeMillis()); if (t2 > 0) { mutex.wait(t2); } } } return altFuture.safeGet(); } }