chromium/chromecast/base/java/src/org/chromium/chromecast/base/Sequencer.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chromecast.base;

import java.util.ArrayDeque;

/**
 * Schedules tasks to run one after another.
 *
 * If nothing is running, scheduled tasks execute synchronously. Any tasks posted by a scheduled
 * task are deferred until the current task finishes. This grants a limited notion of single-
 * threaded atomicity. Note that this is NOT thread-safe!
 */
public class Sequencer {
    private static final int MAX_RECURSION_DEPTH = 65536;
    private boolean mIsRunning;
    private final ArrayDeque<Runnable> mMessageQueue = new ArrayDeque<>();

    /**
     * Runs the task synchronously, or, if a sequence()d task is already running, posts the task
     * to a queue, whose items will be run synchronously when the current task is finished.
     */
    public void sequence(Runnable task) {
        mMessageQueue.add(task);
        if (mIsRunning) return;
        for (int count = 0; !mMessageQueue.isEmpty(); count++) {
            if (count == MAX_RECURSION_DEPTH) {
                throw new InceptionException(
                        "Too many nested sequenced tasks posted from one sequenced call! "
                        + "Is there an infinite loop?");
            }
            sequenceInternal(mMessageQueue.removeFirst());
        }
    }

    private void sequenceInternal(Runnable task) {
        mIsRunning = true;
        task.run();
        mIsRunning = false;
    }

    public boolean inSequence() {
        return mIsRunning;
    }

    /**
     * Thrown if a task in a task in a task... seems to go on forever. Of course we can't detect
     * whether it actually goes on forever because of the halting problem, so we give up after a
     * threshold is exceeded.
     */
    public static class InceptionException extends RuntimeException {
        public InceptionException(String message) {
            super(message);
        }
    }
}