package com.linkedin.parseq.internal;
import java.util.ArrayDeque;
import java.util.Deque;
import com.linkedin.parseq.ParSeqGlobalConfiguration;
/**
* This class allows running the following code structure:
* <pre><code>
* method() {
* action1()
* ...
* actionN()
* }
* </code></pre>
* such that {@code actionX} can throw exception or recursively call {@code method}
* multiple times, without worry about stack overflow.
* <p>
* The guarantee is that actions are called in the same order that recursive approach
* would have called them. To put it in another way, this class guarantees to traversal the
* execution tree in pre-order.
* <p>
* You can imagine recursive invocations as walking an execution tree in DFS order where
* order of visiting children of a tree matters. This class implements DFS walk
* in such a way that used stack is not proportional to the tree height, instead used heap
* is proportional to the tree height.
*
* @author Jaroslaw Odzga (jodzga@linkedin.com)
*
*/
public class Continuations {
private final ThreadLocal<Continuation> CONTINUATION = new ThreadLocal<Continuation>() {
@Override
protected Continuation initialValue() {
return new Continuation();
}
};
public void submit(final Runnable action) {
if (ParSeqGlobalConfiguration.isTrampolineEnabled()) {
doSubmit(action);
} else {
action.run();
}
}
void doSubmit(final Runnable action) {
CONTINUATION.get().submit(action);
}
private static final class Continuation {
// contains sibling actions in reverse order of submission
// sibling actions are actions submitted by the same parent action
private final Deque<Runnable> _siblingActions = new ArrayDeque<>();
// contains actions for execution in pre-order
// actions with larger depth at the top
// for sibling actions with the same depth, the first submitted is at the top
private final Deque<Runnable> _preOrderExecutionStack = new ArrayDeque<>();
private boolean _inLoop = false;
private void submit(final Runnable action) {
if (!_inLoop) {
// we are at the root level of a call tree
// this branch contains main loop responsible for
// executing all actions
_preOrderExecutionStack.push(action);
loop();
} else {
// another child action added by the current action
_siblingActions.push(action);
}
}
private void loop() {
// Entering state:
// - _siblingActions is empty
// - _preOrderExecutionStack has one element
// - _inLoop is false
_inLoop = true;
try {
do {
_preOrderExecutionStack.pop().run();
// currentAction could have submitted a few children actions, so we pop them out from
// _siblingActions & push them into _preOrderExecutionStack, resulting in the desired
// pre-order execution
while (_siblingActions.size() > 0) {
_preOrderExecutionStack.push(_siblingActions.pop());
}
} while (_preOrderExecutionStack.size() > 0);
} finally {
// maintain invariants
_preOrderExecutionStack.clear();
_siblingActions.clear();
_inLoop = false;
}
// Exiting state (even when exception is thrown):
// - _siblingActions is empty
// - _preOrderExecutionStack is empty
// - _inLoop is false
}
}
}