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

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

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

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class DefaultSchedulerContext implements SchedulerContext {
    private final ManualSchedulerContext context;
    private final ScheduledExecutorService executor;
    private final Clock clock;
    private final long offset;
    private ScheduledFuture future;
    private long expectedNext;
    /**
     * When true, indicates that executeNext() is running but has dropped the lock.
     */
    private boolean executingTasks;

    public DefaultSchedulerContext(ScheduledExecutorService executor, Clock clock) {
        this(executor, clock, 0);
    }

    public DefaultSchedulerContext(ScheduledExecutorService executor, Clock clock, long offset) {
        this.context = new ManualSchedulerContext();
        this.executor = executor;
        this.clock = clock;
        this.offset = offset;
        this.expectedNext = Long.MAX_VALUE;
    }

    @Override
    public synchronized TaskHandle schedule(Period period, Task runnable) {
        final TaskHandle handle = context.scheduleAt(period, runnable, clock.now());
        rescheduleNext();

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

            @Override
            public void cancel() {
                handle.cancel();
                rescheduleNext();
            }
        };
    }

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

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

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

    @Override
    public long nextTaskTimestamp() {
        return expectedNext;
    }

    @Override
    public synchronized void cancelAll() {
        context.cancelAll();
        rescheduleNext();
    }

    @Override
    public synchronized void start() {
        context.advanceTo(now());
        context.start();
        scheduleNext();
    }

    private synchronized void rescheduleNext() {
        // If executingTasks is true, whoever sets it to false will complete the future and call scheduleNext() for us.
        if (executingTasks) return;

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

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

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

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

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

    private void executeNext(long next) {
        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;

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy