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

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

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

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class OrderedScheduler implements ClockScheduler {
    private final List contexts = new ArrayList<>();
    private final ScheduledExecutorService executor;
    private final Clock clock;
    private final TaskRunner runner;
    private ScheduledFuture future;
    private List actionsBuffer;
    private volatile long expectedNext = Long.MAX_VALUE;
    /**
     * When true, indicates that executeNext() is running but has dropped the lock.
     */
    private boolean executingTasks;

    public OrderedScheduler() {
        this(Executors.newSingleThreadScheduledExecutor());
    }

    public OrderedScheduler(ScheduledExecutorService executor) {
        this(executor, new SystemClock());
    }

    public OrderedScheduler(ScheduledExecutorService executor, Clock clock) {
        this(executor, clock, null);
    }

    public OrderedScheduler(ScheduledExecutorService executor, Clock clock, TaskRunner runner) {
        this.runner = runner != null ? runner : new TaskRunner.Default();
        this.executor = executor;
        this.clock = clock;
    }

    // This function must always be called within a synchronized block.
    private void rescheduleNext() {
        // If executingTasks is true, whoever sets it to false will complete the future and call scheduleNext() for us.
        if (executingTasks) return;

        // Optimistically attempt to prevent the thread from running. If already running, the check for expectedNext
        // will make it exit.
        if (future != null)
            future.cancel(false);

        // Create a new future for the correct timestamp.
        scheduleNext();
    }

    public synchronized void cancelAll() {
        while (!contexts.isEmpty()) {
            contexts.get(0).cancelAll();
        }
    }

    // This function must always be called within a synchronized block.
    private void scheduleNext() {
        assert Thread.holdsLock(this);

        long now = clock.now();
        final long next = this.expectedNext = nextTaskTimestamp();
        if (next == Long.MAX_VALUE) return;

        future = executor.schedule(() -> executeNext(next), Math.max(next - now, 0), TimeUnit.MILLISECONDS);
    }

    private long nextTaskTimestamp() {
        long next = Long.MAX_VALUE;
        for (MyContext context : contexts) {
            next = Math.min(next, context.nextTaskTimestamp());
        }
        return next;
    }

    /**
     * Returns the timestamp of the currently executing task, or of the next task if none is currently executing.
     */
    public long currentTaskTimestamp() {
        // The expectedNext variable is updated at the end of the task execution, or outside it when rescheduling.
        return expectedNext;
    }

    private void executeNext(long next) {
        List bufferCopy;
        synchronized (this) {
            // There was a race condition with a reschedule, and more than one copy of this code is running.
            if (executingTasks || next != expectedNext) return;

            actionsBuffer = new ArrayList<>();
            for (MyContext context : contexts) {
                context.advanceTo(next);
            }
            bufferCopy = actionsBuffer;
            actionsBuffer = null;

            // We're about to drop the lock
            executingTasks = true;
        }
        try {
            // Run outside the lock
            runner.onTasks(next, bufferCopy);
            assert !Thread.holdsLock(this);
        } finally {
            synchronized (this) {
                // The lock has been reacquired
                executingTasks = false;
                scheduleNext();
            }
        }
    }

    @Override
    public SchedulerContext newContext() {
        return new MyContext();
    }

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

    private class MyContext implements SchedulerContext {
        private final ManualSchedulerContext context;

        private MyContext() {
            this.context = new ManualSchedulerContext((timestamp, actions) -> actionsBuffer.addAll(actions));
        }

        @Override
        public TaskHandle schedule(Period period, Task runnable) {
            TaskHandle result;
            synchronized (OrderedScheduler.this) {
                final TaskHandle handle = context.schedule(period, runnable);
                if (context.started())
                    rescheduleNext();

                result = new TaskHandle() {
                    @Override
                    public long nextTimestamp() {
                        return handle.nextTimestamp();
                    }

                    @Override
                    public void cancel() {
                        synchronized (OrderedScheduler.this) {
                            handle.cancel();
                            if (context.started())
                                rescheduleNext();
                        }
                    }
                };
            }
            return result;
        }

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

        @Override
        public boolean started() {
            return context.started();
        }

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

        @Override
        public void start() {
            synchronized (OrderedScheduler.this) {
                context.advanceTo(clock.now());
                context.start();
                contexts.add(this);
                rescheduleNext();
            }
        }

        @Override
        public void cancelAll() {
            synchronized (OrderedScheduler.this) {
                contexts.remove(this);
                context.cancelAll();
                rescheduleNext();
            }
        }

        public long nextTaskTimestamp() {
            return context.nextTaskTimestamp();
        }

        public void advanceTo(long next) {
            context.advanceTo(next);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy