All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.intelie.pipes.time.ManualSchedulerContext Maven / Gradle / Ivy

There is a newer version: 0.25.5
Show newest version
package net.intelie.pipes.time;

import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ManualSchedulerContext implements SchedulerContext {
    public static final TaskRunner.Default DEFAULT_RUNNER = new TaskRunner.Default();
    /**
     * List of active tasks, ordered by the end of the current window. Non-active tasks are lazily cleaned.
     */
    private final PriorityQueue queue = new PriorityQueue<>();
    /**
     * Temporary buffer to keep tasks before the scheduler's start time is determined.
     */
    private List buffer = new ArrayList<>();
    private final TaskRunner runner;
    private volatile long events;
    private volatile long startTime;
    private long now;
    private int currentIndex;

    private volatile long nextTimestamp = 0;

    public ManualSchedulerContext() {
        this(null);
    }

    public ManualSchedulerContext(TaskRunner runner) {
        this.runner = runner != null ? runner : DEFAULT_RUNNER;
        this.startTime = -1;
    }

    @Override
    public synchronized DefaultTaskHandle schedule(Period period, Task runnable) {
        return scheduleAt(period, runnable, now);
    }

    public synchronized DefaultTaskHandle scheduleAt(Period period, Task runnable, long timestamp) {
        DefaultTaskHandle handle = new DefaultTaskHandle(currentIndex++, events, period, runnable);
        if (!started()) {
            buffer.add(handle);
        } else {
            handle.start(timestamp);
            queueAdd(handle);
        }
        return handle;
    }

    private void queueAdd(DefaultTaskHandle handle) {
        queue.add(handle);
        nextTimestamp = queue.peek().nextTimestamp();
    }

    public synchronized long notifyEvent() {
        return ++events;
    }

    @Override
    public long now() {
        return now;
    }

    @Override
    public boolean started() {
        return startTime >= 0;
    }

    @Override
    public long startTime() {
        return startTime;
    }

    public synchronized long nextTaskTimestamp() {
        DefaultTaskHandle top = peekActive();
        if (top != null)
            return top.nextTimestamp();
        return Long.MAX_VALUE;
    }

    /**
     * Returns the first active task in the priority queue. As a side effect, can discard non-active tasks.
     * This function must be called within a synchronized block
     */
    private DefaultTaskHandle peekActive() {
        assert Thread.holdsLock(this);
        while (!queue.isEmpty() && !queue.peek().active())
            queue.poll();
        return queue.peek();
    }

    @Override
    public synchronized void start() {
        if (!started()) {
            startTime = now();
            for (DefaultTaskHandle handle : buffer) {
                handle.start(startTime);
                queueAdd(handle);
            }
            // The buffer won't be used anymore; release it and let the GC take over.
            buffer = null;
        }
    }

    /**
     * Equivalent to calling advanceTo(timestamp) followed by start().
     */
    public void start(long timestamp) {
        if (!started()) {
            advanceTo(timestamp);
            start();
        }
    }

    /**
     * Advances to the timestamp specified, running all the tasks that expired in the meantime.
     * 

* WARNING: this function will call the runnable passed in the constructor, which will call the tasks. * Unless the caller controls all the tasks, it should avoid calling this function under a lock. */ public void advanceTo(long timestamp) { //not sure if this "if" will break things, hope not if (timestamp < nextTimestamp) { updateNow(timestamp); return; } internalFlush(timestamp, false); } /** * Run the tasks up to a timestamp, or if the last parameter is true, remove all tasks and update the timestamp * to the last task. This function must NOT be called within a synchronized block for this class. */ private void internalFlush(long timestamp, boolean last) { while (true) { List actions; long run; synchronized (this) { if (last && buffer != null) buffer.clear(); DefaultTaskHandle handle = peekActive(); if (handle == null || !last && handle.nextTimestamp() > timestamp) break; actions = new ArrayList<>(); run = handle.window.end(); while (handle != null && handle.window.end() == run) { DefaultTaskHandle task = queue.poll(); if (!last || task.lastEvent != events) { actions.add(new TaskAction(task.window.begin(), task.window.end(), task.runnable)); task.lastEvent = events; } task.window.moveNext(); if (!last) queueAdd(task); handle = peekActive(); } now = run; } // Run any tasks outside the lock assert !Thread.holdsLock(this); if (!actions.isEmpty()) try { runner.onTasks(run, actions); } catch (Throwable e) { Logger.getLogger(ManualSchedulerContext.class.getName()) .log(Level.WARNING, "Uncaught exception", e); } } updateNow(timestamp); } private synchronized void updateNow(long timestamp) { now = Math.max(now, timestamp); } public synchronized Set periods() { Set periods = new LinkedHashSet<>(); for (DefaultTaskHandle handle : queue) periods.add(handle.period); if (buffer != null) for (DefaultTaskHandle handle : buffer) periods.add(handle.period); return periods; } @Override public synchronized void cancelAll() { if (buffer != null) buffer.clear(); queue.clear(); } public void flushAndCancelAll() { internalFlush(0, true); } public static class DefaultTaskHandle implements TaskHandle, Comparable { private final int index; private final Period period; private Task runnable; private volatile boolean active = true; /** * Iterator over the windows of this task's period. */ private PeriodIterator window; private volatile long lastEvent; public DefaultTaskHandle(int index, long lastEvent, Period period, Task runnable) { this.index = index; this.period = period; this.runnable = runnable; this.lastEvent = lastEvent; } @Override public void cancel() { active = false; runnable = null; //GC-friendly } @Override public int compareTo(DefaultTaskHandle that) { if (this.nextTimestamp() != that.nextTimestamp()) return Long.compare(this.nextTimestamp(), that.nextTimestamp()); return Integer.compare(this.index, that.index); } @Override public long nextTimestamp() { return window.end(); } public boolean active() { return active; } /** * Initializes this task's window iterator. Must be called within a synchronized block. */ public void start(long time) { window = new PeriodIterator(time, true, period); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy