com.chiorichan.scheduler.ChioriScheduler.java Source code

Java tutorial

Introduction

Here is the source code for com.chiorichan.scheduler.ChioriScheduler.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * Copyright 2015 Chiori-chan. All Right Reserved.
 * 
 * @author Chiori Greene
 * @email chiorigreene@gmail.com
 */
package com.chiorichan.scheduler;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;

import org.apache.commons.lang3.Validate;

import com.chiorichan.Loader;

public class ChioriScheduler implements IChioriScheduler {
    /**
     * Counter for IDs. Order doesn't matter, only uniqueness.
     */
    private final AtomicInteger ids = new AtomicInteger(1);
    /**
     * Current head of linked-list. This reference is always stale, {@link ChioriTask#next} is the live reference.
     */
    private volatile ChioriTask head = new ChioriTask();
    /**
     * Tail of a linked-list. AtomicReference only matters when adding to queue
     */
    private final AtomicReference<ChioriTask> tail = new AtomicReference<ChioriTask>(head);
    /**
     * Mane thread logic only
     */
    private final PriorityQueue<ChioriTask> pending = new PriorityQueue<ChioriTask>(10,
            new Comparator<ChioriTask>() {
                public int compare(final ChioriTask o1, final ChioriTask o2) {
                    return (int) (o1.getNextRun() - o2.getNextRun());
                }
            });
    /**
     * Mane thread logic only
     */
    private final List<ChioriTask> temp = new ArrayList<ChioriTask>();
    /**
     * These are tasks that are currently active. It's provided for 'viewing' the current state.
     */
    private final ConcurrentHashMap<Integer, ChioriTask> runners = new ConcurrentHashMap<Integer, ChioriTask>();
    private volatile int currentTick = -1;
    private final Executor executor = Executors.newCachedThreadPool();
    private ChioriAsyncDebugger debugHead = new ChioriAsyncDebugger(-1, null, null) {
        @Override
        StringBuilder debugTo(StringBuilder string) {
            return string;
        }
    };
    private ChioriAsyncDebugger debugTail = debugHead;
    private static final int RECENT_TICKS;

    static {
        RECENT_TICKS = 20;
    }

    public int scheduleSyncDelayedTask(final TaskCreator creator, final Runnable task) {
        return this.scheduleSyncDelayedTask(creator, task, 0L);
    }

    public ChioriTask runTask(TaskCreator creator, Runnable runnable) {
        return runTaskLater(creator, runnable, 0L);
    }

    public int scheduleAsyncDelayedTask(final TaskCreator creator, final Runnable task) {
        return this.scheduleAsyncDelayedTask(creator, task, 0L);
    }

    public ChioriTask runTaskAsynchronously(TaskCreator creator, Runnable runnable) {
        return runTaskLaterAsynchronously(creator, runnable, 0L);
    }

    public int scheduleSyncDelayedTask(final TaskCreator creator, final Runnable task, final long delay) {
        return this.scheduleSyncRepeatingTask(creator, task, delay, -1L);
    }

    public ChioriTask runTaskLater(TaskCreator creator, Runnable runnable, long delay) {
        return runTaskTimer(creator, runnable, delay, -1L);
    }

    public int scheduleAsyncDelayedTask(final TaskCreator creator, final Runnable task, final long delay) {
        return this.scheduleAsyncRepeatingTask(creator, task, delay, -1L);
    }

    public ChioriTask runTaskLaterAsynchronously(TaskCreator creator, Runnable runnable, long delay) {
        return runTaskTimerAsynchronously(creator, runnable, delay, -1L);
    }

    public int scheduleSyncRepeatingTask(final TaskCreator creator, final Runnable runnable, long delay,
            long period) {
        return runTaskTimer(creator, runnable, delay, period).getTaskId();
    }

    public ChioriTask runTaskTimer(TaskCreator creator, Runnable runnable, long delay, long period) {
        validate(creator, runnable);
        if (delay < 0L) {
            delay = 0;
        }
        if (period == 0L) {
            period = 1L;
        } else if (period < -1L) {
            period = -1L;
        }
        return handle(new ChioriTask(creator, runnable, nextId(), period), delay);
    }

    public int scheduleAsyncRepeatingTask(final TaskCreator creator, final Runnable runnable, long delay,
            long period) {
        return runTaskTimerAsynchronously(creator, runnable, delay, period).getTaskId();
    }

    public ChioriTask runTaskTimerAsynchronously(TaskCreator creator, Runnable runnable, long delay, long period) {
        validate(creator, runnable);
        if (delay < 0L) {
            delay = 0;
        }
        if (period == 0L) {
            period = 1L;
        } else if (period < -1L) {
            period = -1L;
        }
        return handle(new ChioriAsyncTask(runners, creator, runnable, nextId(), period), delay);
    }

    public <T> Future<T> callSyncMethod(final TaskCreator creator, final Callable<T> task) {
        validate(creator, task);
        final ChioriFuture<T> future = new ChioriFuture<T>(task, creator, nextId());
        handle(future, 0L);
        return future;
    }

    public void cancelTask(final int taskId) {
        if (taskId <= 0) {
            return;
        }
        ChioriTask task = runners.get(taskId);
        if (task != null) {
            task.cancel0();
        }
        task = new ChioriTask(new Runnable() {
            public void run() {
                if (!check(ChioriScheduler.this.temp)) {
                    check(ChioriScheduler.this.pending);
                }
            }

            private boolean check(final Iterable<ChioriTask> collection) {
                final Iterator<ChioriTask> tasks = collection.iterator();
                while (tasks.hasNext()) {
                    final ChioriTask task = tasks.next();
                    if (task.getTaskId() == taskId) {
                        task.cancel0();
                        tasks.remove();
                        if (task.isSync()) {
                            runners.remove(taskId);
                        }
                        return true;
                    }
                }
                return false;
            }
        });
        handle(task, 0L);
        for (ChioriTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
            if (taskPending == task) {
                return;
            }
            if (taskPending.getTaskId() == taskId) {
                taskPending.cancel0();
            }
        }
    }

    public void cancelTasks(final TaskCreator creator) {
        Validate.notNull(creator, "Cannot cancel tasks of null creator");
        final ChioriTask task = new ChioriTask(new Runnable() {
            public void run() {
                check(ChioriScheduler.this.pending);
                check(ChioriScheduler.this.temp);
            }

            void check(final Iterable<ChioriTask> collection) {
                final Iterator<ChioriTask> tasks = collection.iterator();
                while (tasks.hasNext()) {
                    final ChioriTask task = tasks.next();
                    if (task.getOwner().equals(creator)) {
                        task.cancel0();
                        tasks.remove();
                        if (task.isSync()) {
                            runners.remove(task.getTaskId());
                        }
                    }
                }
            }
        });
        handle(task, 0L);
        for (ChioriTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
            if (taskPending == task) {
                return;
            }
            if (taskPending.getTaskId() != -1 && taskPending.getOwner().equals(creator)) {
                taskPending.cancel0();
            }
        }
        for (ChioriTask runner : runners.values()) {
            if (runner.getOwner().equals(creator)) {
                runner.cancel0();
            }
        }
    }

    public void cancelAllTasks() {
        final ChioriTask task = new ChioriTask(new Runnable() {
            public void run() {
                Iterator<ChioriTask> it = ChioriScheduler.this.runners.values().iterator();
                while (it.hasNext()) {
                    ChioriTask task = it.next();
                    task.cancel0();
                    if (task.isSync()) {
                        it.remove();
                    }
                }
                ChioriScheduler.this.pending.clear();
                ChioriScheduler.this.temp.clear();
            }
        });
        handle(task, 0L);
        for (ChioriTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
            if (taskPending == task) {
                break;
            }
            taskPending.cancel0();
        }
        for (ChioriTask runner : runners.values()) {
            runner.cancel0();
        }
    }

    public boolean isCurrentlyRunning(final int taskId) {
        final ChioriTask task = runners.get(taskId);
        if (task == null || task.isSync()) {
            return false;
        }
        final ChioriAsyncTask asyncTask = (ChioriAsyncTask) task;
        synchronized (asyncTask.getWorkers()) {
            return asyncTask.getWorkers().isEmpty();
        }
    }

    public boolean isQueued(final int taskId) {
        if (taskId <= 0) {
            return false;
        }
        for (ChioriTask task = head.getNext(); task != null; task = task.getNext()) {
            if (task.getTaskId() == taskId) {
                return task.getPeriod() >= -1L; // The task will run
            }
        }
        ChioriTask task = runners.get(taskId);
        return task != null && task.getPeriod() >= -1L;
    }

    public List<ChioriWorker> getActiveWorkers() {
        final ArrayList<ChioriWorker> workers = new ArrayList<ChioriWorker>();
        for (final ChioriTask taskObj : runners.values()) {
            // Iterator will be a best-effort (may fail to grab very new values) if called from an async thread
            if (taskObj.isSync()) {
                continue;
            }
            final ChioriAsyncTask task = (ChioriAsyncTask) taskObj;
            synchronized (task.getWorkers()) {
                // This will never have an issue with stale threads; it's state-safe
                workers.addAll(task.getWorkers());
            }
        }
        return workers;
    }

    public List<ChioriTask> getPendingTasks() {
        final ArrayList<ChioriTask> truePending = new ArrayList<ChioriTask>();
        for (ChioriTask task = head.getNext(); task != null; task = task.getNext()) {
            if (task.getTaskId() != -1) {
                // -1 is special code
                truePending.add(task);
            }
        }

        final ArrayList<ChioriTask> pending = new ArrayList<ChioriTask>();
        for (ChioriTask task : runners.values()) {
            if (task.getPeriod() >= -1L) {
                pending.add(task);
            }
        }

        for (final ChioriTask task : truePending) {
            if (task.getPeriod() >= -1L && !pending.contains(task)) {
                pending.add(task);
            }
        }
        return pending;
    }

    /**
     * This method is designed to never block or wait for locks; an immediate execution of all current tasks.
     */
    public void mainThreadHeartbeat(final int currentTick) {
        this.currentTick = currentTick;
        final List<ChioriTask> temp = this.temp;
        parsePending();
        while (isReady(currentTick)) {
            final ChioriTask task = pending.remove();
            if (task.getPeriod() < -1L) {
                if (task.isSync()) {
                    runners.remove(task.getTaskId(), task);
                }
                parsePending();
                continue;
            }
            if (task.isSync()) {
                try {
                    task.run();
                } catch (final Throwable throwable) {
                    Loader.getLogger().log(Level.WARNING, String.format("Task #%s for %s generated an exception",
                            task.getTaskId(), task.getOwner().getName()), throwable);
                }
                parsePending();
            } else {
                debugTail = debugTail.setNext(
                        new ChioriAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass()));
                executor.execute(task);
                // We don't need to parse pending
                // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code)
            }
            final long period = task.getPeriod(); // State consistency
            if (period > 0) {
                task.setNextRun(currentTick + period);
                temp.add(task);
            } else if (task.isSync()) {
                runners.remove(task.getTaskId());
            }
        }
        pending.addAll(temp);
        temp.clear();
        debugHead = debugHead.getNextHead(currentTick);
    }

    private void addTask(final ChioriTask task) {
        final AtomicReference<ChioriTask> tail = this.tail;
        ChioriTask tailTask = tail.get();
        while (!tail.compareAndSet(tailTask, task)) {
            tailTask = tail.get();
        }
        tailTask.setNext(task);
    }

    private ChioriTask handle(final ChioriTask task, final long delay) {
        task.setNextRun(currentTick + delay);
        addTask(task);
        return task;
    }

    private static void validate(final TaskCreator creator, final Object task) {
        Validate.notNull(creator, "TaskCreator cannot be null");
        Validate.notNull(task, "Task cannot be null");
        if (!creator.isEnabled()) {
            throw new IllegalTaskCreatorAccessException("TaskCreator attempted to register task while disabled");
        }
    }

    private int nextId() {
        return ids.incrementAndGet();
    }

    private void parsePending() {
        ChioriTask head = this.head;
        ChioriTask task = head.getNext();
        ChioriTask lastTask = head;
        for (; task != null; task = (lastTask = task).getNext()) {
            if (task.getTaskId() == -1) {
                task.run();
            } else if (task.getPeriod() >= -1L) {
                pending.add(task);
                runners.put(task.getTaskId(), task);
            }
        }
        // We split this because of the way things are ordered for all of the async calls in ChioriScheduler
        // (it prevents race-conditions)
        for (task = head; task != lastTask; task = head) {
            head = task.getNext();
            task.setNext(null);
        }
        this.head = lastTask;
    }

    private boolean isReady(final int currentTick) {
        return !pending.isEmpty() && pending.peek().getNextRun() <= currentTick;
    }

    @Override
    public String toString() {
        int debugTick = currentTick;
        StringBuilder string = new StringBuilder("Recent tasks from ").append(debugTick - RECENT_TICKS).append('-')
                .append(debugTick).append('{');
        debugHead.debugTo(string);
        return string.append('}').toString();
    }
}