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

com.ocadotechnology.event.scheduling.SourceTrackingEventScheduler Maven / Gradle / Ivy

There is a newer version: 16.6.21
Show newest version
/*
 * Copyright © 2017-2023 Ocado (Ocava)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ocadotechnology.event.scheduling;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.util.concurrent.AtomicDouble;
import com.ocadotechnology.event.EventUtil;
import com.ocadotechnology.time.TimeProvider;

/**
 * A simulation-only implementation of EventScheduler which tracks which simulated thread is active in each event,
 * permitting simulated thread handover to be implemented.
 */
public class SourceTrackingEventScheduler extends TypedEventScheduler {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final SimpleDiscreteEventScheduler backingScheduler;

    private final SourceSchedulerTracker tracker;

    private final AtomicBoolean delayed = new AtomicBoolean(false);
    private final List delayedDoNowRunnables = new ArrayList<>();

    private final AtomicDouble delayEndTime = new AtomicDouble(Double.NaN);
    private Cancelable delayEndEvent;

    public SourceTrackingEventScheduler(SourceSchedulerTracker tracker, EventSchedulerType type, SimpleDiscreteEventScheduler backingScheduler) {
        super(type);
        this.backingScheduler = backingScheduler;
        this.tracker = tracker;
    }

    public SourceTrackingEventScheduler createSibling(EventSchedulerType type) {
        return new SourceTrackingEventScheduler(tracker, type, backingScheduler);
    }

    /**
     * Simulates a GC or computational pause in this simulated 'thread'.  If the 'thread' is currently paused, it will
     * start running again at the latest of the current and new pause end times.
     */
    public void delayExecutionUntil(double delayEndTime) {
        delayExecutionUntil(delayEndTime, false);
    }

    /**
     * Stops this scheduler (simulated thread) from executing any tasks before dealyEndTime.
* Other schedulers (simulated threads) are unaffected.

* * There are two return-from-this-call behaviours:
* Blocking behaviour:
* "This method returns when the timeProvider time equals delayEndTime. * No events will have run for this scheduler, but other schedulers (simulated threads) * will have run through all their events up to delayEndTime.
* Any doNow events the caller then adds will run after all overdue delayed events have run * (but before any doNow events those delayed events may add). * Any doAt events the caller adds will run after all delayed events * (we only add doAt events in the future, so this is expected behaviour). * The delayed events will only start executing once the caller event has completed." *
* Non-blocking behaviour:
* "This method returns immediately (with time unchanged). * No events will have run for this scheduler (or any others) as no time has passed.
* Any doNow events the caller then adds will be run first. * Any doAt events the caller adds will be run in time order (as expected), * The caller can schedule events earlier than delayedEndTime (as long as they are after "now") * and they will be executed in order at the maximum of delayedEndTime and their scheduled time. *

* This method is reentrant safe. */ public void delayExecutionUntil(double delayEndTime, boolean blocking) { logger.info("Delaying execution on thread {}: {} until {}", type, (blocking ? "blocking" : "non-blocking"), EventUtil.logTime(delayEndTime)); if (this.delayEndTime.get() >= delayEndTime) { // Either we're trying to delay in the past (in which case delayedEndEvent will be null and we should not delay) // or, we're delaying within a delay (in which case delayedEndEvent will not be null and we're already delaying) return; } delayed.set(true); if (delayEndEvent != null) { delayEndEvent.cancel(); } this.delayEndTime.set(delayEndTime); double delayStartTime = getTimeProvider().getTime(); // Use the backing scheduler so that ending the delay is not itself delayed. Would cause an infinite loop. delayEndEvent = backingScheduler.doAt(delayEndTime, () -> delayFinished(delayStartTime), "Scheduler " + type + (blocking ? " blocking" : " delayed")); if (blocking) { // This causes the scheduler to run through all events up to delayEndTime and call "wrappedDoAt/Now" for each // which adds to the delayedDoNowRunnables. backingScheduler.blockEvent(() -> !delayed.get()); // exitCondition should be true when the delay is over } } private void delayFinished(double delayStartTime) { logger.info("Resuming execution on thread {}. Pause started at {}", type, EventUtil.logTime(delayStartTime)); delayed.set(false); delayEndEvent = null; // The doNow queue will be empty (otherwise we wouldn't have run our doAt). // We don't want to actually run the delayed Runnables now as they'll interfere with the blocking Runnable (if any) // So, we can schedule them on the doNow queue to preserve ordering. delayedDoNowRunnables.stream() .filter(h -> !h.wasCancelled) .forEach(this::doNow); // schedules delayedDoNowRunnables.clear(); } @Override public boolean hasOnlyDaemonEvents() { return backingScheduler.hasOnlyDaemonEvents(); } @Override public void cancel(Event e) { backingScheduler.cancel(e); } @Override public void prepareToStop() { backingScheduler.prepareToStop(); } @Override public void stop() { backingScheduler.stop(); } @Override public boolean isStopping() { return backingScheduler.isStopping(); } @Override public TimeProvider getTimeProvider() { return backingScheduler.getTimeProvider(); } @Override public long getThreadId() { return backingScheduler.getThreadId(); } @Override public double getMinimumTimeDelta() { return backingScheduler.getMinimumTimeDelta(); } @Override public Cancelable doNow(Runnable r, String description) { MutableCancelableHolder cancelableHolder = new MutableCancelableHolder(r, description); return doNow(cancelableHolder); } private Cancelable doNow(MutableCancelableHolder cancelableHolder) { Runnable newRunnable = wrappedForDoNow(cancelableHolder); Cancelable inner = backingScheduler.doNow(newRunnable, cancelableHolder.description); return inner == null ? null : cancelableHolder.setCancelable(inner); } @Override public Cancelable doAt(double time, Runnable r, String description, boolean isDaemon) { MutableCancelableHolder cancelableHolder = new MutableCancelableHolder(r, description); return doAt(time, isDaemon, cancelableHolder); } private Cancelable doAt(double time, boolean isDaemon, MutableCancelableHolder cancelableHolder) { Runnable newRunnable = wrappedForDoAt(cancelableHolder, isDaemon); Cancelable inner = backingScheduler.doAt(time, newRunnable, cancelableHolder.description, isDaemon); return inner == null ? null : cancelableHolder.setCancelable(inner); } @Override public boolean isThreadHandoverRequired() { return !type.equals(tracker.getActiveSchedulerType()); } private Runnable wrappedForDoNow(MutableCancelableHolder cancelableHolder) { return () -> { tracker.setActiveSchedulerType(type); if (delayed.get()) { delayedDoNowRunnables.add(cancelableHolder); return; } cancelableHolder.runnable.run(); }; } private Runnable wrappedForDoAt(MutableCancelableHolder cancelableHolder, boolean isDaemon) { return () -> { tracker.setActiveSchedulerType(type); if (delayed.get()) { doAt(delayEndTime.get(), isDaemon, cancelableHolder); return; } cancelableHolder.runnable.run(); }; } private static final class MutableCancelableHolder implements Cancelable { private final Runnable runnable; private final String description; private Cancelable cancelable; private boolean wasCancelled = false; public MutableCancelableHolder(Runnable runnable, String description) { this.runnable = runnable; this.description = description; } public Cancelable setCancelable(Cancelable cancelable) { this.cancelable = cancelable; return this; } @Override public void cancel() { cancelable.cancel(); wasCancelled = true; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy