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

net.sf.eBus.timer.EScheduledExecutor Maven / Gradle / Ivy

The newest version!
//
// Copyright 2024 Charles W. Rapp
//
// 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 net.sf.eBus.timer;

import com.google.common.base.Strings;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import net.sf.eBus.client.EClient;
import net.sf.eBus.client.EObject;
import net.sf.eBus.config.EConfigure;
import net.sf.eBus.config.EConfigure.ScheduledExecutor;
import static net.sf.eBus.config.ThreadType.SPINYIELD;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Executes given task, {@link EObject} pairs at a specified
 * time. When task timer expires, the task and {@code EObject}
 * are {@link EClient#dispatch(Runnable, EObject) dispatched}
 * via the eBus run queue.
 * 

* Schedule methods are used to create timers with various * delays, returning a timer object which can be used to * cancel the timer or check its status. Methods * {@link #scheduleAtFixedRate(Runnable, EObject, Duration, Duration)} * and * {@link #scheduleWithFixedDelay(Runnable, EObject, Duration, Duration)} * create and execute timer tasks which run periodically until * canceled. *

*

* Unlike {@code java.util.concurrent.ScheduledExecutorService}, * eBus scheduled executor does not support delay or * period < zero. A repeating fixed delay or repeating fixed * rate period must be > zero. A zero single-shot delay or * initial delay must be ≥ zero. *

*

* All scheduled methods accept relative delays and * periods as arguments and not absolute times or date. *

*

Creating an EScheduledTimer

* eBus scheduled timers can be created in two ways: one at start * up by defining them in * {@code -Dnet.sf.eBus.config.jsonFile= scheduledExecutors} * block or programmatically using * {@code ScheduleExecutorBuilder} to create a * {@code ScheduledExecutor} instance which is then passed to * {@link #newScheduledExecutor(EConfigure.ScheduledExecutor)}. * This method returns the {@code EScheduledExecutor} instance * based on the given configuration. See * {@link net.sf.eBus.config.EConfigure.ScheduledExecutor} for * details on configuring an eBus scheduled executor. *

* Note eBus scheduled executors are required to have a unique * name. This allows eBus scheduled executor instances to be * retrieved using {@link #getExecutor(String)}. An attempt to * create an executor with a named equaling an existing * executor's will result in an exception. *

*

Usage Example

* This code example assumes that an eBus scheduled executor * named "fast-timer" has already been created. *
import java.time.Duration;
import java.time.Instant;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.client.EFeedState;
import net.sf.eBus.client.EScheduledExecutor;
import net.sf.eBus.client.ESubscribeFeed;
import net.sf.eBus.client.ESubscriber;
import net.sf.eBus.client.IESubscribeFeed;
import net.sf.eBus.client.IETimer;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ENotificationMessage;

public class MySubscriber implements ESubscriber {
    private static final String EXECUTOR_NAME = "fast-timer";

    // Verify that at least one update is received over the past minute.
    private static final Duration TIMER_DELAY = Duration.ofMinutes(1L);

    private final EMessageKey mKey;
    private final FeedScope mScope;
    private ESubscribeFeed mFeed;
    private Instant mLatestUpdate;
    private IETimer mUpdateTimer;

    public MySubscriber(final String subject, final FeedScope scope) {
        mKey = new EMessageKey(AppUpdate.class, subject);
        mScope = scope;
    }

    @Override public void startup() {
        mFeed = (ESubscribeFeed.builder()).target(this)
                                          .messageKey(mKey)
                                          .scope(mScope)
                                          .statusCallback(this::onFeedStatus)
                                          .notifyCallback(this::onUpdate)
                                          .build();
        mFeed.subscribe();
    }

    @Override public void shutdown() {
        if (mFeed != null) {
            mFeed.close();
            mFeed = null;
        }

        stopTimer();
    }

    private void onFeedStatus(final EFeedState state, final IESubscribeFeed feed) {
        // If the feed is now down, then stop the timer.
        if (state == EFeedState.DOWN) {
            stopTimer();
        } else {
            final EScheduleExecutor executor = EScheduledExecutor.getExecutor(EXECUTOR_NAME);

            mUpdateTimer = executor.scheduleWithFixedDelay(this::onTimeout, this, TIMER_DELAY, TIMER_DELAY);
            mLatestUpdate = Instant.now();
        }
    }

    private void onUpdate(final AppUpdate update) {
        mLatestUpdate = update.timestampAsInstant();
        ...
    }

    private void onTimeout() {
        final Duration delta = Duration.between(mLatestUpdate, Instant.now());

        // Did an update arrive within the last minute?
        if (delta.compareTo(TIMER_DELAY) > 0) {
            // Yes. Take necessary actions.
            ...
        }
    }

    private void stopTimer() {
        if (mUpdateTimer != null) {
            try {
                mTimer.close();
            } catch (Exception jex) {
                // Do nothing.
            } finally {
                mTimer = null;
                mLatestUpdate = null;
            }
        }
    }
}
* * @see EScheduledExecutorBlocking * @see EScheduledExecutorSpinning * @see EScheduledExecutorSpinSleep * @see EScheduledExecutorSpinYield * @see net.sf.eBus.config.EConfigure.ScheduledExecutor * @see net.sf.eBus.config.EConfigure.ScheduledExecutorBuilder * * @author Charles W. Rapp */ public abstract class EScheduledExecutor extends Thread { //--------------------------------------------------------------- // Member enums. // /** * An eBus-scheduled timer may be in one of three states: *
    *
  • * waiting for an active timer to expire, *
  • *
  • * timer has expired and is no longer active, and *
  • *
  • * timer is canceled, will never expire, and is no longer * active. *
  • *
*/ public enum TimerState { /** * Waiting for active timer to expire. */ ACTIVE (true), /** * Timer successfully reached expiration and is no longer * active. */ EXPIRED (false), /** * Timer is canceled, will never expire, and is no longer * active. */ CANCELED (false); //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Set to {@code true} if state means timer has yet to * expire or be canceled. */ private final boolean mIsActive; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // private TimerState(final boolean isActive) { mIsActive = isActive; } // end of TimerState(boolean) // // end of Constructors //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // public boolean isActive() { return (mIsActive); } // end of isActive() // // end of Get Methods. //------------------------------------------------------- } // end of enum TimerState /** * Enumerates supported timer type: single shot, repeated at * a fixed rate, and repeated at a fixed delay. */ public enum TimerType { /** * Timer expires only once and then is permanently * inactive. */ SINGLE_SHOT, /** * Timer repeats at a fixed rate based on when the timer * was initially scheduled. Subsequent expirations will * be scheduled on the initial timestamp + (n * period). * This timer is best used for "alarm clock" timers which * need to expire at a certain wall clock time. */ REPEAT_FIXED_RATE, /** * Timer repeats at fixed delay based on when timer * expires. Subsequence expirations will be scheduled * on the current time + periodic delay. This timer is * best used for events which must occur at a fixed delay * independent of wall clock time. */ REPEAT_FIXED_DELAY; } // end of enum TimerType /** * Provides reason for a timer no longer running. */ public enum CancelReason { /** * Timer is still running. */ NOT_CANCELED, /** * A single-shot timer is no longer running due to its * sole expiration. */ SINGLE_SHOT_TIMER_EXPIRED, /** * Timer canceled due to user request. */ USER_REQUEST, /** * Timer canceled due to associated timer task throwing * an exception. */ TASK_EXCEPTION, /** * Timer canceled due to associated executor shut down. */ EXECUTOR_SHUTDOWN } // end of enum CancelReason //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // private static final String NULL_TASK = "task is null"; private static final String NULL_CLIENT = "client is null"; private static final String NULL_DELAY = "delay is null"; private static final String NULL_INIT_DELAY = "initial delay is null"; private static final String NULL_PERIOD = "period is null"; private static final String NEGATIVE_DELAY = "delay < zero"; private static final String NEGATIVE_INIT_DELAY = "initial delay < zero"; private static final String NEGATIVE_PERIOD = "period <= zero"; private static final String EXEC_SHUT_DOWN = "scheduled executor is shut down"; //----------------------------------------------------------- // Statics. // /** * Maps unique scheduled executor name to its instance. */ private static final Map sScheduledExecutors = new ConcurrentHashMap<>(); /** * Logging subsystem interface. */ private static final Logger sLogger = LoggerFactory.getLogger(EScheduledExecutor.class); //----------------------------------------------------------- // Locals. // /** * Contains active timers yet to expire. */ protected final ConcurrentSkipListSet mTimers; /** * Continue running while this flag is {@code true}. */ protected volatile boolean mRunFlag; /** * Decrement this signal when thread is started. */ private CountDownLatch mStartupSignal; /** * Decrement this signal when thread is stopped. */ private final CountDownLatch mShutdownSignal; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new eBus scheduled executor instance. * @param config contains scheduled executor configuration. * @param startupSignal decrement signal when scheduled * executor thread is up and running. * @param shutdownSignal decrement signal when scheduled * executor thread is shut down. */ protected EScheduledExecutor(final ScheduledExecutor config, final CountDownLatch startupSignal, final CountDownLatch shutdownSignal) { super (config.name()); mTimers = new ConcurrentSkipListSet<>(); mRunFlag = false; mStartupSignal = startupSignal; mShutdownSignal = shutdownSignal; } // end of EScheduledExecutor(...) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Declarations. // /** * Adds a new eBus timer to timers set in such a way as to * notify {@link #waitForExpiration(ETimerImpl)} if * the new timer expires before the current next-to-expire * timer. * @param timer new timer to be added to timers set. */ protected abstract void addTimer(ETimerImpl timer); /** * Removes given timer from timers set in such a way as to * notify {@link #waitForExpiration(ETimerImpl)} if * removed timer is currently next to expire. * @param timer timer removed from timers set. */ protected abstract void removeTimer(ETimerImpl timer); /** * Returns next eBus timer scheduled to expire. Will return * {@code null} if this scheduled executor is stopped. * @return next timer scheduled to expire or {@code null} if * executor is stopped. */ protected abstract @Nullable ETimerImpl pollTimer(); /** * Returns {@code true} if given eBus timer expired and * {@code false} if the routine was preempted by either this * scheduled timer stopping or a new timer was scheduled and * is due to expire before this timer. * @param timer next timer to expire. * @return {@code true} if {@code timer} expires and * {@code false} if not. */ protected abstract boolean waitForExpiration(ETimerImpl timer); // // end of Abstract Method Declarations. //----------------------------------------------------------- //----------------------------------------------------------- // Thread Method Overrides. // @Override public final void run() { ETimerImpl timer; mRunFlag = true; // Decrement start up signal to let creator know this // thread is up and running. mStartupSignal.countDown(); mStartupSignal = null; // Continue processing timers until this scheduled // executor is stopped. while (mRunFlag) { // Get the next scheduled timer to expire. Wait if // necessary when there are no scheduled timers. timer = pollTimer(); // Wait for timer to expire or executor to shut down. // Note: pollTimer() returns null when run flag is // false. if (timer != null && mRunFlag && waitForExpiration(timer)) { dispatchExpiredTimers(); } } // Cancel all remaining timers and remove from timers // set. cancelAllTimers(); // Decrement shutdown signal to let shutdown() method // know this thread is dead. mShutdownSignal.countDown(); } // end of run() // // end of Thread Method Overrides. //----------------------------------------------------------- //----------------------------------------------------------- // Get Methods. // /** * Returns {@code true} if this eBus scheduled executor is * running and {@code false} if shutdown. * @return {@code true} if this executor is running. */ public boolean isRunning() { return (mRunFlag); } // end of isRunning() /** * Returns {@code true} if eBus scheduled executor with the * given name exists and {@code false} otherwise. * @param name eBus scheduled executor name. * @return {@code true} if eBus scheduled executor named * {@code name} exists. */ public static boolean isExecutor(final String name) { return (sScheduledExecutors.containsKey(name)); } // end of isExecutor(String) /** * Returns scheduled executor associated with the given name. * Returns {@code null} if there is no scheduled executor * associated with the name. * @param name scheduled executor name. * @return scheduled executor named {@code name}. * @throws IllegalArgumentException * if {@code name} is {@code null} or an empty string. */ public static @Nullable EScheduledExecutor getExecutor(final String name) { if (Strings.isNullOrEmpty(name)) { throw ( new IllegalArgumentException( "name is null or empty")); } return (sScheduledExecutors.get(name)); } // end of getExecutor(String) // // end of Get Methods. //----------------------------------------------------------- //----------------------------------------------------------- // Set Methods. // /** * Shuts down this executor thread which results in all * currently scheduled timers being canceled. */ public void shutdown() { // Set run flag to false and then wake up this thread // so it returns to main run() while loop. mRunFlag = false; signalShutdown(); // Wait here for thread to stop. try { mShutdownSignal.await(); } catch (InterruptedException interrupt) {} } // end of shutdown() // // end of Set Methods. //----------------------------------------------------------- //----------------------------------------------------------- // Schedule Methods. // /** * Submits a single-shot task which expires after the given * delay. * @param task execute this task when timer expires. * @param eobject dispatch task to this eBus client's queue. * @param delay timer expires after this delay. * @return an {@code IETimer} instance which can be used to * cancel the timer prior to execution. * @throws NullPointerException * if {@code task}, {@code eobject}, or {@code delay} is * {@code null}. * @throws RejectedExecutionException * if {@code delay} < zero. * @throws IllegalStateException * if executor is shut down. */ public IETimer schedule(final Runnable task, final EObject eobject, final Duration delay) { final long expiration; final SingleShotTimer retval; // Make sure parameters are not null. Objects.requireNonNull(task, NULL_TASK); Objects.requireNonNull(eobject, NULL_CLIENT); Objects.requireNonNull(delay, NULL_DELAY); // Make sure delay is >= zero. if (delay.compareTo(Duration.ZERO) < 0) { throw ( new RejectedExecutionException(NEGATIVE_DELAY)); } if (!mRunFlag) { throw (new IllegalStateException(EXEC_SHUT_DOWN)); } final EClient client = EClient.findOrCreateClient( eobject, EClient.ClientLocation.LOCAL); expiration = (System.nanoTime() + delay.toNanos()); // Create timer task and store in timer priority queue. retval = new SingleShotTimer(task, client, expiration); client.addTimer(retval); addTimer(retval); return (retval); } // end of scheduleAtFixedRate(Runnable, EObject, Duration) /** * Submits a periodic task which expires for the first time * after the initial delay and then repeatedly at the * periodic rate. In other words, expirations are * {@code initialDelay}, then {@code initialDelay + period}, * then {@code initialDelay + (2 * period)}, and so on. *

* The given task will continue to be indefinitely executed * until one of the following occurs: *

*
    *
  • * The task is explicitly canceled via the returned * {@link IETimer} instance. *
  • *
  • * The client is garbage collected. *
  • *
  • * The executor is terminated which results in all * scheduled tasks being canceled. *
  • *
  • * The task's execution results in a thrown exception. *
  • *
* Once a timer is canceled, subsequent executions are * suppressed and {@link IETimer#isDone() isDone} returns * {@code true}. *

* If any task execution takes longer than its period, then * subsequent executions may start late but will not * result in multiple scheduled expirations. *

* @param task execute this task when timer expires. * @param eobject dispatch task to this eBus client's queue. * @param initialDelay timer first expiration after this * delay. * @param period timer subsequent expirations after this * period. * @return {@code IETimer} instance which can be used to * cancel the timer prior to execution. */ public IETimer scheduleAtFixedRate(final Runnable task, final EObject eobject, final Duration initialDelay, final Duration period) { final long expiration; final FixedRateTimer retval; // Make sure parameters are not null. Objects.requireNonNull(task, NULL_TASK); Objects.requireNonNull(eobject, NULL_CLIENT); Objects.requireNonNull( initialDelay, NULL_INIT_DELAY); Objects.requireNonNull(period, NULL_PERIOD); // Make sure initial delay is >= zero. if (initialDelay.compareTo(Duration.ZERO) < 0) { throw ( new RejectedExecutionException( NEGATIVE_INIT_DELAY)); } // Make sure period is > zero. if (period.compareTo(Duration.ZERO) <= 0) { throw ( new RejectedExecutionException(NEGATIVE_PERIOD)); } if (!mRunFlag) { throw (new IllegalStateException(EXEC_SHUT_DOWN)); } final EClient client = EClient.findOrCreateClient( eobject, EClient.ClientLocation.LOCAL); expiration = (System.nanoTime() + initialDelay.toNanos()); // Create timer task and store in timer priority queue. retval = new FixedRateTimer(task, client, expiration, period); client.addTimer(retval); addTimer(retval); return (retval); } // end of scheduleAtFixedRate(Runnable, EObject, Duration, Duration) /** * Submits a periodic task which expires for the first time * after the initial delay and then repeatedly with the given * delay between the termination of the previous expiration * and the commencement of the next. This means that task * execution time does not impact scheduling the subsequent * expirations. When the task completes, the next expiration * is current time plus delay. *

* The given task will continue to be indefinitely executed * until one of the following occurs: *

*
    *
  • * The task is explicitly canceled via the returned * {@link IETimer} instance. *
  • *
  • * The client is garbage collected. *
  • *
  • * The executor is terminated which results in all * scheduled tasks being canceled. *
  • *
  • * The task's execution results in a thrown exception. *
  • *
* Once a timer is canceled, subsequent executions are * suppressed and {@link IETimer#isDone() isDone} returns * {@code true}. * @param task execute this task when timer expires. * @param eobject dispatch task to this eBus client's queue. * @param initialDelay timer first expiration after this * delay. * @param delay timer subsequence expirations after this * delay. * @return {@code IETimer} instance which can be used to * cancel the timer prior to execution. */ public IETimer scheduleWithFixedDelay(final Runnable task, final EObject eobject, final Duration initialDelay, final Duration delay) { final long expiration; final FixedDelayTimer retval; // Make sure parameters are not null. Objects.requireNonNull(task, NULL_TASK); Objects.requireNonNull(eobject, NULL_DELAY); Objects.requireNonNull( initialDelay, NULL_INIT_DELAY); Objects.requireNonNull(delay, NULL_DELAY); // Make sure initial delay is >= zero. if (initialDelay.compareTo(Duration.ZERO) < 0) { throw ( new RejectedExecutionException( NEGATIVE_INIT_DELAY)); } // Make sure repeating delay is > zero. if (delay.compareTo(Duration.ZERO) <= 0) { throw ( new RejectedExecutionException("delay <= zero")); } if (!mRunFlag) { throw (new IllegalStateException(EXEC_SHUT_DOWN)); } final EClient client = EClient.findOrCreateClient( eobject, EClient.ClientLocation.LOCAL); expiration = (System.nanoTime() + initialDelay.toNanos()); // Create timer task and store in timer priority queue. retval = new FixedDelayTimer(task, client, expiration, delay); client.addTimer(retval); addTimer(retval); return (retval); } // end of scheduleWithFixedDelay(...) // // end of Schedule Methods. //----------------------------------------------------------- /** * Loads each of the schedule executors contained in * {@link EConfigure#scheduledExecutors()} map. * @param config contains schedule executor configurations. */ public static void loadScheduledExecutors(final EConfigure config) { final Collection configs = (config.scheduledExecutors()).values(); for (ScheduledExecutor c : configs) { newScheduledExecutor(c); } } // end of loadScheduledExecutors(EConfigure) /** * Returns a new eBus scheduled executor based on the given * configuration. The returned executor is fully started and * ready to accept timer scheduling. * @param config scheduled executor configuration. * @return new eBus scheduled executor * @throws IllegalArgumentException * if {@code config} contains a duplicate scheduled executor * name. */ public static EScheduledExecutor newScheduledExecutor(final ScheduledExecutor config) { final CountDownLatch startupSignal = new CountDownLatch(1); final CountDownLatch shutdownSignal = new CountDownLatch(1); final EScheduledExecutor retval; switch (config.threadType()) { case BLOCKING: retval = new EScheduledExecutorBlocking( config, startupSignal, shutdownSignal); break; case SPINNING: retval = new EScheduledExecutorSpinning( config, startupSignal, shutdownSignal); break; case SPINYIELD: retval = new EScheduledExecutorSpinYield( config, startupSignal, shutdownSignal); break; default: retval = new EScheduledExecutorSpinSleep( config, startupSignal, shutdownSignal); } sScheduledExecutors.put(retval.getName(), retval); // Get the scheduled executor thread up and running // before returning. retval.start(); try { startupSignal.await(); } catch (InterruptedException interrupt) {} return (retval); } /** * Returns next timer scheduled to expire or {@code null} if * there are no scheduled timers. * @return next timer scheduled to expire. */ protected final @Nullable ETimerImpl nextTimer() { ETimerImpl retval = null; try { retval = (ETimerImpl) mTimers.first(); } catch (NoSuchElementException elemex) { // Return null; } return (retval); } // end of nextTimer() /** * Dispatches all expired timers in the timers set. Removes * any expired or canceled timers found along the way. */ private void dispatchExpiredTimers() { final List repeatTimers = new ArrayList<>(); final Iterator timerIt = mTimers.iterator(); ETimerImpl timer; long nanoTime; boolean continueFlag = true; while (continueFlag && timerIt.hasNext()) { timer = (ETimerImpl) timerIt.next(); nanoTime = System.nanoTime(); continueFlag = (timer.expiration() < nanoTime); // Is this timer active? if (timer.isDone()) { // No. Remove from the timers set. This timer // was canceled just prior to expiration and not // yet removed from the timers set. timerIt.remove(); } // Is this timer expired? else if (continueFlag) { // Yes. Dispatch timer task. timer.dispatch(); // Remove this timer from the timers set. If it // is a repeating timer it will be placed back // into the set when it is rescheduled. timerIt.remove(); // Add all timers to repeating timers list. // Single shot timer will mark itself expired // and not be added back to timers set. repeatTimers.add(timer); } } // Need to reschedule timers outside of the above loop // since rescheduling updates the timers set - which // above loop is iterating over. for (ETimerImpl t : repeatTimers) { if (t.reschedule()) { addTimer(t); } } } // end of dispatchExpiredTimers() /** * Cancels all active timers and clears the timers set. */ private void cancelAllTimers() { final List timers = new ArrayList<>(mTimers); ETimerImpl eTimer; mTimers.clear(); for (IETimer t : timers) { eTimer = (ETimerImpl) t; if (!eTimer.isDone()) { eTimer.setTimerState( TimerState.CANCELED, CancelReason.EXECUTOR_SHUTDOWN, null); } } } // end of cancelAllTimers() /** * Interrupts this thread in case timer is currently parked. */ private void signalShutdown() { this.interrupt(); } // end of signalShutdown() //--------------------------------------------------------------- // Inner classes. // /** * A scheduled timer task which may be active, expired, or * canceled. Once a timer is expired or canceled, it will * never expire again. */ public static interface IETimer extends AutoCloseable, Comparable { /** * Returns timer current state. * @return timer current state. * * @see #isCanceled() * @see #isDone() */ TimerState timerState(); /** * Returns timer type. * @return timer type. */ TimerType timerType(); /** * Returns remaining delay until this timer expires. If * timer is either expired (and not a repeating timer) or * if timer is cancelled, then returns * {@link Duration#ZERO}. * @return remaining delay until timer expires. */ Duration delay(); /** * Returns {@code true} if this task was successfully * canceled prior to expiration. Note that this task * may be inactive due to expiration. * @return {@code true} if task is canceled. * * @see #timerState() * @see #isDone() */ boolean isCanceled(); /** * Returns reason why timer is no longer running. * @return timer cancellation reason. */ CancelReason cancelReason(); /** * Returns exception associated with * {@link #cancelReason()} returning * {@link CancelReason#TASK_EXCEPTION}. Returns * {@code null} for any other cancel reason. * @return task exception. */ @Nullable Exception cancelException(); /** * Returns {@code true} if this task is no longer active. * This could be due to either expiration or * cancellation, there is no distinction between the * two. * @return {@code true} if task is no longer active. * * @see #timerState() * @see #isCanceled() */ boolean isDone(); } // end of interface IETimer /** * Implements {@IETimer} instance and contains necessary * information to execute expire timer task on the associated * eBus client task queue and cancel timer. */ /* package */ abstract class ETimerImpl implements IETimer { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Specific timer type. */ protected final TimerType mType; /** * Current timer state. */ protected final AtomicReference mTimerState; /** * Timer set to expire at this nanosecond time based on * {@code System.nanoTime()}. This data member is not * {@code final} because it is updated by repeat timers. */ protected long mExpiration; /** * When timer expires, execute this task. */ private final Runnable mTask; /** * eBus client setting this timer. */ private final EClient mClient; /** * Reason for timer no longer running. */ private CancelReason mCxlReason; /** * Timer task exception is thrown. */ private Exception mCxlException; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // protected ETimerImpl(final Runnable task, final EClient client, final long expiration, final TimerType type) { mTask = task; mClient = client; mExpiration = expiration; mType = type; mTimerState = new AtomicReference<>(TimerState.ACTIVE); } // end of ETimerImple(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Abstract Method Declarations. // /** * Reschedules a repeat timer. Returns {@code true} if * this is a repeat timer and {@code false} if * single-shot. * @return {@code true} if timer re-scheduled. */ protected abstract boolean reschedule(); // // end of Abstract Method Declarations. //------------------------------------------------------- //------------------------------------------------------- // IETimer Interface Implementation. // @Override public final TimerState timerState() { return (mTimerState.get()); } // end of timerState() @Override public final TimerType timerType() { return (mType); } // end of timerType() /** * Returns active timer expiration. Returns * {@link Duration#ZERO} if timer is inactive. * @return time until timer expiration. */ @Override public final Duration delay() { final long delta = (mExpiration - System.nanoTime()); final Duration retval; // Is this timer expired or canceled? if (mTimerState.get() != TimerState.ACTIVE || delta <= 0L) { // Yes. Return zero. retval = Duration.ZERO; } else { retval = Duration.ofNanos(delta); } return (retval); } // end of delay() @Override public final boolean isCanceled() { return (mTimerState.get() == TimerState.CANCELED); } // end of isCanceled() @Override public final boolean isDone() { return (!((mTimerState.get()).isActive())); } // end of isDone() /** * Cancels running timer * @throws Exception */ @Override public final void close() throws Exception { // Was this timer still active when canceled? if (mTimerState.compareAndSet(TimerState.ACTIVE, TimerState.CANCELED)) { // Yes. Remove this timer from timer set. removeTimer(this); setTimerState(TimerState.CANCELED, CancelReason.USER_REQUEST, null); } } // end of close() @Override public final CancelReason cancelReason() { return (mCxlReason); } // end of cancelReason() @Override public final @Nullable Exception cancelException() { return (mCxlException); } // end of cancelException() // // end of IETimer Interface Implementation. //------------------------------------------------------- //------------------------------------------------------- // Comparable Interface Implementation. // /** * Returns value <, equal to, or greater than zero * based on whether {@code this IETimer}'s expiration is * <, equal to; or greater than {@code timer}'s * expiration. * @param timer compared timer. * @return integer values <, equal to, or > zero. */ @Override public final int compareTo(final IETimer timer) { final ETimerImpl eTimer = (ETimerImpl) timer; return ( Long.compare(mExpiration, eTimer.expiration())); } // end of compareTo(IETimer) // // end of Comparable Interface Implementation. //------------------------------------------------------- //------------------------------------------------------- // Object Method Overrides. // @Override public String toString() { return ( String.format( "[type=%s, state=%s, delay=%,d]", mType, mTimerState.get(), expiration())); } // end of toString() // // end of Object Method Overrides. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns timer's expiration in nanosecond with respect * to {@code System.nanoTime()}. * @return timer nanosecond expiration. */ /* package */ long expiration() { return (mExpiration); } // end of expiration() /** * Returns timer task. * @return timer task. */ private Runnable task() { return (mTask); } // end of task() // // end of Get Methods. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // /** * Sets timer to given state. * @param state next timer state. * @param reason reason timer is canceled. * @param optional exception associated with timer * cancellation. */ protected void setTimerState(final TimerState state, final CancelReason reason, final @Nullable Exception cxlException) { mTimerState.set(state); mCxlReason = reason; mCxlException = cxlException; if (!state.isActive()) { mClient.removeTimer(this); } } // end of setTimerState(TimerState) /** * Automatically cancels timer due to a timer task * exception. * @param cxlException timer task exception. */ private void close(final Exception cxlException) { // Was this timer still active when canceled? if (mTimerState.compareAndSet(TimerState.ACTIVE, TimerState.CANCELED)) { // Yes. Remove this timer from timer set. removeTimer(this); setTimerState(TimerState.CANCELED, CancelReason.TASK_EXCEPTION, cxlException); } } // end of close(Exception) // // end of Set Methods. //------------------------------------------------------- /** * Dispatches client's timer task. */ private void dispatch() { if (mTimerState.get() == TimerState.ACTIVE) { // Put the timer within another timer task so if // timer throws an exception, the exception can // be caught and timer canceled. mClient.dispatch(new TimerTask(this)); } } // end of dispatch() } // end of class ETimerImpl private final class SingleShotTimer extends ETimerImpl { //----------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // private SingleShotTimer(final Runnable task, final EClient client, final long expiration) { super (task, client, expiration, TimerType.SINGLE_SHOT); } // end of SingleShotTimer(Runnable, EClient, long) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Abstract Method Implementations. // /** * Does nothing since single-shot timers are not * rescheduled. * @return {@code false}. */ @SuppressWarnings ({"java:S1186"}) @Override protected boolean reschedule() { // Mark this timer as expired. setTimerState(TimerState.EXPIRED, CancelReason.SINGLE_SHOT_TIMER_EXPIRED, null); return (false); } // end of reschedule() // // end of Abstract Method Implementations. //------------------------------------------------------- } // end of class SingleShotTimer private final class FixedRateTimer extends ETimerImpl { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Repeat expirations are based on this expiration, * period, and expiration count. */ private final long mInitialExpiration; /** * Subsequent expirations occur at this nanosecond * period. */ private final long mPeriod; /** * Tracks number of times this timer has expired. Used * to calculate next expiration. */ private int mExpirationCount; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // private FixedRateTimer(final Runnable task, final EClient client, final long expiration, final Duration period) { super (task, client, expiration, TimerType.REPEAT_FIXED_RATE); mInitialExpiration = expiration; mPeriod = period.toNanos(); mExpirationCount = 0; } // end of FixedRateTimer(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Abstract Method Implementations. // /** * Schedules the next expiration at a fixed rate. * @return {@code true} if timer active and expiration * rescheduled and {@code false} if timer is not active. */ @SuppressWarnings ({"java:S1186"}) @Override protected boolean reschedule() { final boolean retcode = (mTimerState.get()).isActive(); // Is this timer still active? if (retcode) { // Yes. Set the next expiration. ++mExpirationCount; mExpiration = (mInitialExpiration + (mPeriod * mExpirationCount)); } return (retcode); } // end of reschedule() // // end of Abstract Method Implementations. //------------------------------------------------------- } // end of class FixedRateTimer private final class FixedDelayTimer extends ETimerImpl { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Subsequent expirations occur at this nanosecond * period. */ private final long mPeriod; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // private FixedDelayTimer(final Runnable task, final EClient client, final long expiration, final Duration period) { super (task, client, expiration, TimerType.REPEAT_FIXED_RATE); mPeriod = period.toNanos(); } // end of FixedDelayTimer(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Abstract Method Implementations. // /** * Schedules the next expiration at a fixed rate. * @return {@code true} if timer active and expiration * rescheduled and {@code false} if timer is not active. */ @SuppressWarnings ({"java:S1186"}) @Override protected boolean reschedule() { final boolean retcode = (mTimerState.get()).isActive(); // Is this timer still active? if (retcode) { // Yes. Set the next expiration based on the // current nanosecond time. mExpiration = (System.nanoTime() + mPeriod); } return (retcode); } // end of reschedule() // // end of Abstract Method Implementations. //------------------------------------------------------- } // end of class FixedDelayTimer /** * Encapsulates scheduled timer task and calls * {@code mTask.run} within this class' {@code run} method. * These point is that if {@code mTask.run} throws an * exception, that exception is caught, logged, and the timer * is canceled if still active. */ private static final class TimerTask implements Runnable { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Expired timer. */ private final ETimerImpl mTimer; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new timer task encapsulating given timer. * @param timer expired timer. */ private TimerTask(final ETimerImpl timer) { mTimer = timer; } // end of TimerTask(ETimerImpl) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Runnable Interface Implementation. // /** * Executes encapsulated timer task. If timer task throws * an exception, then timer is canceled. */ @Override public void run() { try { (mTimer.task()).run(); } catch (Exception jex) { sLogger.warn( "timer task exception; canceling timer", jex); mTimer.close(jex); } } // end of run() // // end of Runnable Interface Implementation. //------------------------------------------------------- } // end of class TimerTask } // end of class EScheduledExecutor




© 2015 - 2024 Weber Informatics LLC | Privacy Policy