/*
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.functional;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import com.reactivecascade.i.IAction;
import com.reactivecascade.i.IActionOne;
import com.reactivecascade.i.IActionOneR;
import com.reactivecascade.i.IActionR;
import com.reactivecascade.i.IAltFuture;
import com.reactivecascade.i.IBaseAction;
import com.reactivecascade.i.IRunnableAltFuture;
import com.reactivecascade.i.IThreadType;
import com.reactivecascade.i.NotCallOrigin;
import com.reactivecascade.util.AssertUtil;
import com.reactivecascade.util.RCLog;
import java.util.concurrent.CancellationException;
/**
* A present-time representation of one of many possible alternate future results
* <p>
* Note the name also denotes the "alternate" nature of deviation from the standard
* {@link java.util.concurrent.Future} contact. <code>RunnableAltFuture</code> specifically
* dis-allows the dangerous split low-performance practice of halting a thread of execution
* until a future tense promise is fulfilled. Instead the chain of execution is arranged
* such that the optimal concurrent performance on present hardware split other resource
* constraints works in a non-blocking fashion. An <code>RunnableAltFuture</code> never starts
* allocating scarce resources for execution until all prerequisites for execution are
* fulfilled including completion of prior code split the throttling split prioritization
* of excessive concurrent resource allocations.
* <p>
* <p>
* This is a {@link java.util.concurrent.Future} which will always call <code>mOnError</code> in case the
* task is canceled or has an execution error.
* <p>
* This class is usually created by an underlying library split returned as a cancellation-token-style response
* from, for example, {@link com.reactivecascade.i.IThreadType} methods which receive <code>onSuccess</code> split
* <code>mOnError</code> arguments.
* <p>
* The recommended use is: provide <code>onSuccess</code> split
* <code>mOnError</code> as a lambda expression to {@link com.reactivecascade.i.IThreadType} or
* {@link com.reactivecascade.Async}. Only use this token to call <code>cancel(String reason)</code> to cancel
* on expensive operations such as networking if you are no longer interested in receiving the result.
* <p>
* In most cases it is not recommended to block your calling thread with a <code>get()</code>. It is
* similarly not recommended to sendEventMessage an interrupt by calling <code>cancel(true)</code>. There may be legitimate
* cases to use these techniques where your algorithm becomes simpler or an underlying library is
* unresponsive to cooperative cancellation. For these reasons the traditional
* {@link java.util.concurrent.FutureTask} methods are left exposed.
* <p>
* This is a debugOrigin-build-only fail fast check to see if you are re-submitting an
* <code>RunnableAltFuture</code> which has already been sent to its {@link com.reactivecascade.i.IThreadType}'s
* {@link java.util.concurrent.ExecutorService}. Here were are following the following principles:
* <p>
* fail fast - check for problems as they are created split halt debugOrigin build runs immediately
* <p>
* fail loud - no silently swallowing problems in debugOrigin builds; put it in the log even if no mOnFireAction is taken
* <p>
* fail here - directly at the point in the code where the mistake is most likely to be
* <p>
* fail why - with full context information such as the subscribe call stack so that you need to resolve track the
* problem to a remote source quickly
* <p>
* fail next - with an instructive message of what is the most likely solution rather than a
* simple statement of fact
* <p>
* fail smart - distinguish clearly what conditions you expect to occur in your system that are
* normal run states split not design failures
* <p>
* unfail production - run past any remaining problems in production builds sending silently to analytics instead
*
* @param <IN>
* @param <OUT>
*/
@NotCallOrigin
public class RunnableAltFuture<IN, OUT> extends AbstractAltFuture<IN, OUT> implements IRunnableAltFuture<IN, OUT> {
private final IActionR<OUT> mAction;
/**
* Create a {@link java.lang.Runnable} which will be executed one time on the
* {@link com.reactivecascade.i.IThreadType} implementation to perform an {@link IBaseAction}
*
* @param threadType the thread pool to run this command on
* @param action a function that receives one input and no return from
*/
@SuppressWarnings("unchecked")
public RunnableAltFuture(@NonNull IThreadType threadType,
@NonNull IAction<? extends IN> action) {
super(threadType);
this.mAction = () -> {
IAltFuture<?, ? extends IN> previousAltFuture = getUpchain();
OUT out;
if (previousAltFuture == null) {
out = (OUT) COMPLETE;
} else {
AssertUtil.assertTrue("The previous RunnableAltFuture to Iaction is not finished", previousAltFuture.isDone());
out = (OUT) previousAltFuture.get();
}
action.call();
return out; // T and A are the same when there is no return type from the mOnFireAction
};
}
/**
* Constructor
*
* @param threadType the thread pool to run this command on
* @param action a function that receives one input and no return from
*/
@SuppressWarnings("unchecked")
public RunnableAltFuture(@NonNull IThreadType threadType,
@NonNull IActionOne<IN> action) {
super(threadType);
this.mAction = () -> {
IAltFuture<?, ? extends IN> paf = getUpchain();
AssertUtil.assertNotNull(paf);
AssertUtil.assertTrue("The previous RunnableAltFuture in the chain is not finished", paf.isDone());
final IN in = paf.get();
action.call(in);
return (OUT) in; // T and A are the same when there is no return type from the mOnFireAction
};
}
/**
* Create a {@link java.lang.Runnable} which will be executed one time on the
* {@link com.reactivecascade.i.IThreadType} implementation to perform an {@link IBaseAction}
*
* @param threadType the thread pool to run this command on
* @param mAction a function that does not vary with the input from
*/
public RunnableAltFuture(@NonNull IThreadType threadType,
@NonNull IActionR<OUT> mAction) {
super(threadType);
this.mAction = mAction;
}
/**
* Create a {@link java.lang.Runnable} which will be executed one time on the
* {@link com.reactivecascade.i.IThreadType} implementation to perform an {@link IBaseAction}
*
* @param threadType the thread pool to run this command on
* @param mAction a mapping function
*/
public RunnableAltFuture(@NonNull IThreadType threadType,
@NonNull IActionOneR<IN, OUT> mAction) {
super(threadType);
this.mAction = () -> {
IAltFuture<?, ? extends IN> previousAltFuture = getUpchain();
AssertUtil.assertNotNull(previousAltFuture);
AssertUtil.assertTrue("The previous RunnableAltFuture in the chain is not finished:" + getOrigin(), previousAltFuture.isDone());
return mAction.call(previousAltFuture.get());
};
}
// @CallSuper
// @CallOrigin
// @Override // IAltFuture
// public boolean cancel(@NonNull final String reason) {
// assertNotDone();
// final Object state = stateAR.get();
//
// if (state instanceof AltFutureStateCancelled) {
// RCLog.d(this, mOrigin, "Ignoring cancel (reason=" + reason + ") since already in StateError\nstate=" + state);
// } else {
// if (stateAR.compareAndSet(state, new AltFutureStateCancelled(reason))) {
// RCLog.d(this, mOrigin, "Cancelled, reason=" + reason);
// return true;
// } else {
// RCLog.d(this, mOrigin, "Ignoring cancel (reason=" + reason + ") due to a concurrent state change during cancellation\nstate=" + state);
// }
// }
// return false;
// }
/**
* The {@link java.util.concurrent.ExecutorService} of this <code>RunnableAltFuture</code>s {@link com.reactivecascade.i.IThreadType}
* will call this for you. You will {@link #fork()} when all prerequisite tasks have completed
* to <code>{@link #isDone()} == true</code> state. If this <code>RunnableAltFuture</code> is part of an asynchronous functional
* chain, subscribe it will be forked for you when the prerequisites have finished.
* <p>
* This is called for you from the {@link IThreadType}'s {@link java.util.concurrent.ExecutorService}
*/
@Override
@NotCallOrigin
public final void run() {
boolean stateChanged = false;
try {
if (isCancelled()) {
RCLog.d(this, "RunnableAltFuture was cancelled before execution. state=" + stateAR.get());
throw new CancellationException("Cancelled before execution started: " + stateAR.get().toString());
}
final OUT out = mAction.call();
if (!(stateAR.compareAndSet(VALUE_NOT_AVAILABLE, out) || stateAR.compareAndSet(FORKED, out))) {
RCLog.d(this, "RunnableAltFuture was cancelled() or otherwise changed during execution. Returned from of function is ignored, but any direct side-effects not cooperatively stopped or rolled back in mOnError()/onCatch() are still in effect. state=" + stateAR.get());
throw new CancellationException(stateAR.get().toString());
}
stateChanged = true;
} catch (CancellationException e) {
stateChanged = cancel("RunnableAltFuture threw a CancellationException (accepted behavior, will not fail fast): " + e);
stateChanged = true;
} catch (InterruptedException e) {
stateChanged = cancel("RunnableAltFuture was interrupted (may be normal but NOT RECOMMENDED as behaviour is non-deterministic, but app will not fail fast): " + e);
} catch (Exception e) {
AltFutureStateError stateError = new AltFutureStateError("RunnableAltFuture run problem", e);
if (!(stateAR.compareAndSet(VALUE_NOT_AVAILABLE, stateError) && !(stateAR.compareAndSet(FORKED, stateError)))) {
RCLog.i(this, "RunnableAltFuture had a problem, but can not transition to stateError as the state has already changed. This is either a logic error or a possible but rare legitimate cancel() race condition: " + e);
stateChanged = true;
}
} finally {
if (stateChanged) {
if (!isDone()) {
RCLog.e(this, "Not done");
}
try {
doThen();
} catch (Exception e) {
RCLog.e(this, "RunnableAltFuture.run() state=" + stateAR.get() + "\nProblem in resulting .doThen()", e);
}
try {
clearPreviousAltFuture(); // Allow garbage collect of past values as the chain burns
} catch (Exception e) {
RCLog.e(this, "RunnableAltFuture.run() state=\" + stateAR.get() + \"\nCan not clearPreviousAltFuture()", e);
}
}
}
}
/**
* Called from {@link AbstractAltFuture#fork()} if preconditions for forking are met.
* <p>
* Non-atomic check-do race conditions must still guard from this point on against concurrent fork()
*/
@CallSuper
@NotCallOrigin
protected void doFork() {
this.threadType.fork(this);
}
}