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 } } }