1 /*
   2  * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.concurrent;
  27 
  28 import javafx.beans.property.BooleanProperty;
  29 import javafx.beans.property.IntegerProperty;
  30 import javafx.beans.property.ObjectProperty;
  31 import javafx.beans.property.ReadOnlyIntegerProperty;
  32 import javafx.beans.property.ReadOnlyIntegerWrapper;
  33 import javafx.beans.property.ReadOnlyObjectProperty;
  34 import javafx.beans.property.ReadOnlyObjectWrapper;
  35 import javafx.beans.property.SimpleBooleanProperty;
  36 import javafx.beans.property.SimpleIntegerProperty;
  37 import javafx.beans.property.SimpleObjectProperty;
  38 import javafx.util.Callback;
  39 import javafx.util.Duration;
  40 import java.util.Timer;
  41 import java.util.TimerTask;
  42 
  43 /**
  44  * <p>The ScheduledService is a {@link Service} which will automatically restart
  45  * itself after a successful execution, and under some conditions will
  46  * restart even in case of failure. A new ScheduledService begins in
  47  * the READY state, just as a normal Service. After calling
  48  * <code>start</code> or <code>restart</code>, the ScheduledService will
  49  * enter the SCHEDULED state for the duration specified by <code>delay</code>.
  50  * </p>
  51  *
  52  * <p>Once RUNNING, the ScheduledService will execute its Task. On successful
  53  * completion, the ScheduledService will transition to the SUCCEEDED state,
  54  * and then to the READY state and back to the SCHEDULED state. The amount
  55  * of time the ScheduledService will remain in this state depends on the
  56  * amount of time between the last state transition to RUNNING, and the
  57  * current time, and the <code>period</code>. In short, the <code>period</code>
  58  * defines the minimum amount of time from the start of one run and the start of
  59  * the next. If the previous execution completed before <code>period</code> expires,
  60  * then the ScheduledService will remain in the SCHEDULED state until the period
  61  * expires. If on the other hand the execution took longer than the
  62  * specified period, then the ScheduledService will immediately transition
  63  * back to RUNNING. </p>
  64  *
  65  * <p>If, while RUNNING, the ScheduledService's Task throws an error or in
  66  * some other way ends up transitioning to FAILED, then the ScheduledService
  67  * will either restart or quit, depending on the values for
  68  * <code>backoffStrategy</code>, <code>restartOnFailure</code>, and
  69  * <code>maximumFailureCount</code>.</p>
  70  *
  71  * <p>If a failure occurs and <code>restartOnFailure</code> is false, then
  72  * the ScheduledService will transition to FAILED and will stop. To restart
  73  * a failed ScheduledService, you must call restart manually.</p>
  74  *
  75  * <p>If a failure occurs and <code>restartOnFailure</code> is true, then
  76  * the the ScheduledService <em>may</em> restart automatically. First,
  77  * the result of calling <code>backoffStrategy</code> will become the
  78  * new <code>cumulativePeriod</code>. In this way, after each failure, you can cause
  79  * the service to wait a longer and longer period of time before restarting.
  80  * Once the task completes successfully, the cumulativePeriod is reset to
  81  * the value of <code>period</code>.</p>
  82  *
  83  * <p>ScheduledService defines static EXPONENTIAL_BACKOFF_STRATEGY and LOGARITHMIC_BACKOFF_STRATEGY
  84  * implementations, of which LOGARITHMIC_BACKOFF_STRATEGY is the default value for
  85  * backoffStrategy. After <code>maximumFailureCount</code> is reached, the
  86  * ScheduledService will transition to FAILED in exactly the same way as if
  87  * <code>restartOnFailure</code> were false.</p>
  88  *
  89  * <p>If the <code>period</code> or <code>delay</code> is changed while the
  90  * ScheduledService is running, the new values will be taken into account on the
  91  * next iteration. For example, if the <code>period</code> is increased, then the next time the
  92  * ScheduledService enters the SCHEDULED state, the new <code>period</code> will be used.
  93  * Likewise, if the <code>delay</code> is changed, the new value will be honored on
  94  * the next restart or reset/start.</p>
  95  *
  96  * The ScheduledService is typically used for use cases that involve polling. For
  97  * example, you may want to ping a server on a regular basis to see if there are
  98  * any updates. Such as ScheduledService might be implemented like this:
  99  *
 100  * <pre><code>
 101  * {@literal ScheduledService<Document> svc = new ScheduledService<Document>()} {
 102  *     {@literal protected Task<Document> createTask()} {
 103  *         {@literal return new Task<Document>()} {
 104  *             protected Document call() {
 105  *                 // Connect to a Server
 106  *                 // Get the XML document
 107  *                 // Parse it into a document
 108  *                 return document;
 109  *             }
 110  *         };
 111  *     }
 112  * };
 113  * svc.setPeriod(Duration.seconds(1));
 114  * </code></pre>
 115  *
 116  * This example will ping the remote server every 1 second.
 117  *
 118  * <p>Timing for this class is not absolutely reliable. A very busy event thread might introduce some timing
 119  * lag into the beginning of the execution of the background Task, so very small values for the period or
 120  * delay are likely to be inaccurate. A delay or period in the hundreds of milliseconds or larger should be
 121  * fairly reliable.</p>
 122  *
 123  * <p>The ScheduledService in its default configuration has a default <code>period</code> of 0 and a
 124  * default <code>delay</code> of 0. This will cause the ScheduledService to execute the task immediately
 125  * upon {@link #start()}, and re-executing immediately upon successful completion.</p>
 126  *
 127  * <p>For this purposes of this class, any Duration that answers true to {@link javafx.util.Duration#isUnknown()}
 128  * will treat that duration as if it were Duration.ZERO. Likewise, any Duration which answers true
 129  * to {@link javafx.util.Duration#isIndefinite()} will be treated as if it were a duration of Double.MAX_VALUE
 130  * milliseconds. Any null Duration is treated as Duration.ZERO. Any custom implementation of an backoff strategy
 131  * callback must be prepared to handle these different potential values.</p>
 132  *
 133  * <p>The ScheduledService introduces a new property called {@link #lastValue}. The lastValue is the value that
 134  * was last successfully computed. Because a Service clears its {@code value} property on each run, and
 135  * because the ScheduledService will reschedule a run immediately after completion (unless it enters the
 136  * cancelled or failed states), the value property is not overly useful on a ScheduledService. In most cases
 137  * you will want to instead use the value returned by lastValue.</p>
 138  *
 139  * <b>Implementer Note:</b> The {@link #ready()}, {@link #scheduled()}, {@link #running()}, {@link #succeeded()},
 140  * {@link #cancelled()}, and {@link #failed()} methods are implemented in this class. Subclasses which also
 141  * override these methods must take care to invoke the super implementation.
 142  *
 143  * @param <V> The computed value of the ScheduledService
 144  * @since JavaFX 8.0
 145  */
 146 public abstract class ScheduledService<V> extends Service<V> {
 147     /**
 148      * A Callback implementation for the <code>backoffStrategy</code> property which
 149      * will exponentially backoff the period between re-executions in the case of
 150      * a failure. This computation takes the original period and the number of
 151      * consecutive failures and computes the backoff amount from that information.
 152      *
 153      * <p>If the {@code service} is null, then Duration.ZERO is returned. If the period is 0 then
 154      * the result of this method will simply be {@code Math.exp(currentFailureCount)}. In all other cases,
 155      * the returned value is the same as {@code period + (period * Math.exp(currentFailureCount))}.</p>
 156      */
 157     public static final Callback<ScheduledService<?>, Duration> EXPONENTIAL_BACKOFF_STRATEGY
 158             = new Callback<ScheduledService<?>, Duration>() {
 159         @Override public Duration call(ScheduledService<?> service) {
 160             if (service == null) return Duration.ZERO;
 161             final double period = service.getPeriod() == null ? 0 : service.getPeriod().toMillis();
 162             final double x = service.getCurrentFailureCount();
 163             return Duration.millis(period == 0 ? Math.exp(x) : period + (period * Math.exp(x)));
 164         }
 165     };
 166 
 167     /**
 168      * A Callback implementation for the <code>backoffStrategy</code> property which
 169      * will logarithmically backoff the period between re-executions in the case of
 170      * a failure. This computation takes the original period and the number of
 171      * consecutive failures and computes the backoff amount from that information.
 172      *
 173      * <p>If the {@code service} is null, then Duration.ZERO is returned. If the period is 0 then
 174      * the result of this method will simply be {@code Math.log1p(currentFailureCount)}. In all other cases,
 175      * the returned value is the same as {@code period + (period * Math.log1p(currentFailureCount))}.</p>
 176      */
 177     public static final Callback<ScheduledService<?>, Duration> LOGARITHMIC_BACKOFF_STRATEGY
 178             = new Callback<ScheduledService<?>, Duration>() {
 179         @Override public Duration call(ScheduledService<?> service) {
 180             if (service == null) return Duration.ZERO;
 181             final double period = service.getPeriod() == null ? 0 : service.getPeriod().toMillis();
 182             final double x = service.getCurrentFailureCount();
 183             return Duration.millis(period == 0 ? Math.log1p(x) : period + (period * Math.log1p(x)));
 184         }
 185     };
 186 
 187     /**
 188      * A Callback implementation for the <code>backoffStrategy</code> property which
 189      * will linearly backoff the period between re-executions in the case of
 190      * a failure. This computation takes the original period and the number of
 191      * consecutive failures and computes the backoff amount from that information.
 192      *
 193      * <p>If the {@code service} is null, then Duration.ZERO is returned. If the period is 0 then
 194      * the result of this method will simply be {@code currentFailureCount}. In all other cases,
 195      * the returned value is the same as {@code period + (period * currentFailureCount)}.</p>
 196      */
 197     public static final Callback<ScheduledService<?>, Duration> LINEAR_BACKOFF_STRATEGY
 198             = new Callback<ScheduledService<?>, Duration>() {
 199         @Override public Duration call(ScheduledService<?> service) {
 200             if (service == null) return Duration.ZERO;
 201             final double period = service.getPeriod() == null ? 0 : service.getPeriod().toMillis();
 202             final double x = service.getCurrentFailureCount();
 203             return Duration.millis(period == 0 ? x : period + (period * x));
 204         }
 205     };
 206 
 207     /**
 208      * This Timer is used to schedule the delays for each ScheduledService. A single timer
 209      * ought to be able to easily service thousands of ScheduledService objects.
 210      */
 211     private static final Timer DELAY_TIMER = new Timer("ScheduledService Delay Timer", true);
 212 
 213     /**
 214      * The initial delay between when the ScheduledService is first started, and when it will begin
 215      * operation. This is the amount of time the ScheduledService will remain in the SCHEDULED state,
 216      * before entering the RUNNING state, following a fresh invocation of {@link #start()} or {@link #restart()}.
 217      */
 218     private ObjectProperty<Duration> delay = new SimpleObjectProperty<>(this, "delay", Duration.ZERO);
 219     public final Duration getDelay() { return delay.get(); }
 220     public final void setDelay(Duration value) { delay.set(value); }
 221     public final ObjectProperty<Duration> delayProperty() { return delay; }
 222 
 223     /**
 224      * The minimum amount of time to allow between the start of the last run and the start of the next run.
 225      * The actual period (also known as <code>cumulativePeriod</code>)
 226      * will depend on this property as well as the <code>backoffStrategy</code> and number of failures.
 227      */
 228     private ObjectProperty<Duration> period = new SimpleObjectProperty<>(this, "period", Duration.ZERO);
 229     public final Duration getPeriod() { return period.get(); }
 230     public final void setPeriod(Duration value) { period.set(value); }
 231     public final ObjectProperty<Duration> periodProperty() { return period; }
 232 
 233     /**
 234      * Computes the amount of time to add to the period on each failure. This cumulative amount is reset whenever
 235      * the the ScheduledService is manually restarted.
 236      */
 237     private ObjectProperty<Callback<ScheduledService<?>,Duration>> backoffStrategy =
 238             new SimpleObjectProperty<>(this, "backoffStrategy", LOGARITHMIC_BACKOFF_STRATEGY);
 239     public final Callback<ScheduledService<?>,Duration> getBackoffStrategy() { return backoffStrategy.get(); }
 240     public final void setBackoffStrategy(Callback<ScheduledService<?>, Duration> value) { backoffStrategy.set(value); }
 241     public final ObjectProperty<Callback<ScheduledService<?>,Duration>> backoffStrategyProperty() { return backoffStrategy; }
 242 
 243     /**
 244      * Indicates whether the ScheduledService should automatically restart in the case of a failure in the Task.
 245      */
 246     private BooleanProperty restartOnFailure = new SimpleBooleanProperty(this, "restartOnFailure", true);
 247     public final boolean getRestartOnFailure() { return restartOnFailure.get(); }
 248     public final void setRestartOnFailure(boolean value) { restartOnFailure.set(value); }
 249     public final BooleanProperty restartOnFailureProperty() { return restartOnFailure; }
 250 
 251     /**
 252      * The maximum number of times the ScheduledService can fail before it simply ends in the FAILED
 253      * state. You can of course restart the ScheduledService manually, which will cause the current
 254      * count to be reset.
 255      */
 256     private IntegerProperty maximumFailureCount = new SimpleIntegerProperty(this, "maximumFailureCount", Integer.MAX_VALUE);
 257     public final int getMaximumFailureCount() { return maximumFailureCount.get(); }
 258     public final void setMaximumFailureCount(int value) { maximumFailureCount.set(value); }
 259     public final IntegerProperty maximumFailureCountProperty() { return maximumFailureCount; }
 260 
 261     /**
 262      * The current number of times the ScheduledService has failed. This is reset whenever the
 263      * ScheduledService is manually restarted.
 264      */
 265     private ReadOnlyIntegerWrapper currentFailureCount = new ReadOnlyIntegerWrapper(this, "currentFailureCount", 0);
 266     public final int getCurrentFailureCount() { return currentFailureCount.get(); }
 267     public final ReadOnlyIntegerProperty currentFailureCountProperty() { return currentFailureCount.getReadOnlyProperty(); }
 268     private void setCurrentFailureCount(int value) {
 269         currentFailureCount.set(value);
 270     }
 271 
 272     /**
 273      * The current cumulative period in use between iterations. This will be the same as <code>period</code>,
 274      * except after a failure, in which case the result of the backoffStrategy will be used as the cumulative period
 275      * following each failure. This is reset whenever the ScheduledService is manually restarted or an iteration
 276      * is successful. The cumulativePeriod is modified when the ScheduledService enters the scheduled state.
 277      * The cumulativePeriod can be capped by setting the {@code maximumCumulativePeriod}.
 278      */
 279     private ReadOnlyObjectWrapper<Duration> cumulativePeriod = new ReadOnlyObjectWrapper<>(this, "cumulativePeriod", Duration.ZERO);
 280     public final Duration getCumulativePeriod() { return cumulativePeriod.get(); }
 281     public final ReadOnlyObjectProperty<Duration> cumulativePeriodProperty() { return cumulativePeriod.getReadOnlyProperty(); }
 282     void setCumulativePeriod(Duration value) { // package private for testing
 283         // Make sure any null value is turned into ZERO
 284         Duration newValue = value == null || value.toMillis() < 0 ? Duration.ZERO : value;
 285         // Cap the newValue based on the maximumCumulativePeriod.
 286         Duration maxPeriod = maximumCumulativePeriod.get();
 287         if (maxPeriod != null && !maxPeriod.isUnknown() && !newValue.isUnknown()) {
 288             if (maxPeriod.toMillis() < 0) {
 289                 newValue = Duration.ZERO;
 290             } else if (!maxPeriod.isIndefinite() && newValue.greaterThan(maxPeriod)) {
 291                 newValue = maxPeriod;
 292             }
 293         }
 294         cumulativePeriod.set(newValue);
 295     }
 296 
 297     /**
 298      * The maximum allowed value for the cumulativePeriod. Setting this value will help ensure that in the case of
 299      * repeated failures the back-off algorithm doesn't end up producing unreasonably large values for
 300      * cumulative period. The cumulative period is guaranteed not to be any larger than this value. If the
 301      * maximumCumulativePeriod is negative, then cumulativePeriod will be capped at 0. If maximumCumulativePeriod
 302      * is NaN or null, then it will not influence the cumulativePeriod.
 303      */
 304     private ObjectProperty<Duration> maximumCumulativePeriod = new SimpleObjectProperty<>(this, "maximumCumulativePeriod", Duration.INDEFINITE);
 305     public final Duration getMaximumCumulativePeriod() { return maximumCumulativePeriod.get(); }
 306     public final void setMaximumCumulativePeriod(Duration value) { maximumCumulativePeriod.set(value); }
 307     public final ObjectProperty<Duration> maximumCumulativePeriodProperty() { return maximumCumulativePeriod; }
 308 
 309     /**
 310      * The last successfully computed value. During each iteration, the "value" of the ScheduledService will be
 311      * reset to null, as with any other Service. The "lastValue" however will be set to the most recently
 312      * successfully computed value, even across iterations. It is reset however whenever you manually call
 313      * reset or restart.
 314      */
 315     private ReadOnlyObjectWrapper<V> lastValue = new ReadOnlyObjectWrapper<>(this, "lastValue", null);
 316     public final V getLastValue() { return lastValue.get(); }
 317     public final ReadOnlyObjectProperty<V> lastValueProperty() { return lastValue.getReadOnlyProperty(); }
 318 
 319     /**
 320      * The timestamp of the last time the task was run. This is used to compute the amount
 321      * of delay between successive iterations by taking the cumulativePeriod into account.
 322      */
 323     private long lastRunTime = 0L;
 324 
 325     /**
 326      * Whether or not this iteration is a "fresh start", such as the initial call to start,
 327      * or a call to restart, or a call to reset followed by a call to start.
 328      */
 329     private boolean freshStart = true;
 330 
 331     /**
 332      * This is a TimerTask scheduled with the DELAY_TIMER. All it does is kick off the execution
 333      * of the actual background Task.
 334      */
 335     private TimerTask delayTask = null;
 336 
 337     /**
 338      * This is set to false when the "cancel" method is called, and reset to true on "reset".
 339      * We need this so that any time the developer calls 'cancel', even when from within one
 340      * of the event handlers, it will cause us to transition to the cancelled state.
 341      */
 342     private boolean stop = false;
 343 
 344     // This method is invoked by Service to actually execute the task. In the normal implementation
 345     // in Service, this method will simply delegate to the Executor. In ScheduledService, however,
 346     // we instead will delay the correct amount of time before we finally invoke executeTaskNow,
 347     // which is where we end up delegating to the executor.
 348     @Override protected void executeTask(final Task<V> task) {
 349         assert task != null;
 350         checkThread();
 351 
 352         if (freshStart) {
 353             // The delayTask should have concluded and been made null by this point.
 354             // If not, then somehow we were paused waiting for another iteration and
 355             // somebody caused the system to run again. However resetting things should
 356             // have cleared the delayTask.
 357             assert delayTask == null;
 358 
 359             // The cumulativePeriod needs to be initialized
 360             setCumulativePeriod(getPeriod());
 361 
 362             // Pause for the "delay" amount of time and then execute
 363             final long d = (long) normalize(getDelay());
 364             if (d == 0) {
 365                 // If the delay is zero or null, then just start immediately
 366                 executeTaskNow(task);
 367             } else {
 368                 schedule(delayTask = createTimerTask(task), d);
 369             }
 370         } else {
 371             // We are executing as a result of an iteration, not a fresh start.
 372             // If the runPeriod (time between the last run and now) exceeds the cumulativePeriod, then
 373             // we need to execute immediately. Otherwise, we will pause until the cumulativePeriod has
 374             // been reached, and then run.
 375             double cumulative = normalize(getCumulativePeriod()); // Can never be null.
 376             double runPeriod = clock() - lastRunTime;
 377             if (runPeriod < cumulative) {
 378                 // Pause and then execute
 379                 assert delayTask == null;
 380                 schedule(delayTask = createTimerTask(task), (long) (cumulative - runPeriod));
 381             } else {
 382                 // Execute immediately
 383                 executeTaskNow(task);
 384             }
 385         }
 386     }
 387 
 388     /**
 389      * {@inheritDoc}
 390      *
 391      * Implementation Note: Subclasses which override this method must call this super implementation.
 392      */
 393     @Override protected void succeeded() {
 394         super.succeeded();
 395         lastValue.set(getValue());
 396         // Reset the cumulative time
 397         Duration d = getPeriod();
 398         setCumulativePeriod(d);
 399         // Have to save this off, since it will be reset here in a second
 400         final boolean wasCancelled = stop;
 401         // Call the super implementation of reset, which will not cause us
 402         // to think this is a new fresh start.
 403         superReset();
 404         assert freshStart == false;
 405         // If it was cancelled then we will progress from READY to SCHEDULED to CANCELLED so that
 406         // the lifecycle changes are predictable according to the Service specification.
 407         if (wasCancelled) {
 408             cancelFromReadyState();
 409         } else {
 410             // Fire it up!
 411             start();
 412         }
 413     }
 414 
 415     /**
 416      * {@inheritDoc}
 417      *
 418      * Implementation Note: Subclasses which override this method must call this super implementation.
 419      */
 420     @Override protected void failed() {
 421         super.failed();
 422         assert delayTask == null;
 423         // Restart as necessary
 424         setCurrentFailureCount(getCurrentFailureCount() + 1);
 425         if (getRestartOnFailure() && getMaximumFailureCount() > getCurrentFailureCount()) {
 426             // We've not yet maxed out the number of failures we can
 427             // encounter, so we're going to iterate
 428             Callback<ScheduledService<?>,Duration> func = getBackoffStrategy();
 429             if (func != null) {
 430                 Duration d = func.call(this);
 431                 setCumulativePeriod(d);
 432             }
 433 
 434             superReset();
 435             assert freshStart == false;
 436             start();
 437         } else {
 438             // We've maxed out, so do nothing and things will just stop.
 439         }
 440     }
 441 
 442     /**
 443      * {@inheritDoc}
 444      *
 445      * Implementation Note: Subclasses which override this method must call this super implementation.
 446      */
 447     @Override public void reset() {
 448         super.reset();
 449         stop = false;
 450         setCumulativePeriod(getPeriod());
 451         lastValue.set(null);
 452         setCurrentFailureCount(0);
 453         lastRunTime = 0L;
 454         freshStart = true;
 455     }
 456 
 457     /**
 458      * Cancels any currently running task and stops this scheduled service, such that
 459      * no additional iterations will occur.
 460      *
 461      * @return whether any running task was cancelled, false if no task was cancelled.
 462      *         In any case, the ScheduledService will stop iterating.
 463      */
 464     @Override public boolean cancel() {
 465         boolean ret = super.cancel();
 466         stop = true;
 467         if (delayTask != null) {
 468             delayTask.cancel();
 469             delayTask = null;
 470         }
 471         return ret;
 472     }
 473 
 474     /**
 475      * This method exists only for testing purposes. The normal implementation
 476      * will delegate to a java.util.Timer, however during testing we want to simply
 477      * inspect the value for the delay and execute immediately.
 478      * @param task not null
 479      * @param delay &gt;= 0
 480      */
 481     void schedule(TimerTask task, long delay) {
 482         DELAY_TIMER.schedule(task, delay);
 483     }
 484 
 485     /**
 486      * This method only exists for the sake of testing.
 487      * @return freshStart
 488      */
 489     boolean isFreshStart() { return freshStart; }
 490 
 491     /**
 492      * Gets the time of the current clock. At runtime this is simply getting the results
 493      * of System.currentTimeMillis, however during testing this is hammered so as to return
 494      * a time that works well during testing.
 495      * @return The clock time
 496      */
 497     long clock() {
 498         return System.currentTimeMillis();
 499     }
 500 
 501     /**
 502      * Called by this class when we need to avoid calling this class' implementation of
 503      * reset which has the side effect of resetting the "freshStart", currentFailureCount,
 504      * and other state.
 505      */
 506     private void superReset() {
 507         super.reset();
 508     }
 509 
 510     /**
 511      * Creates the TimerTask used for delaying execution. The delay can either be due to
 512      * the initial delay (if this is a freshStart), or it can be the computed delay in order
 513      * to execute the task on its fixed schedule.
 514      *
 515      * @param task must not be null.
 516      * @return the delay TimerTask.
 517      */
 518     private TimerTask createTimerTask(final Task<V> task) {
 519         assert task != null;
 520         return new TimerTask() {
 521             @Override public void run() {
 522                 Runnable r = () -> {
 523                     executeTaskNow(task);
 524                     delayTask = null;
 525                 };
 526 
 527                 // We must make sure that executeTaskNow is called from the FX thread.
 528                 // This must happen on th FX thread because the super implementation of
 529                 // executeTask is going to call getExecutor so it can use any user supplied
 530                 // executor, and this property can only be read on the FX thread.
 531                 if (isFxApplicationThread()) {
 532                     r.run();
 533                 } else {
 534                     runLater(r);
 535                 }
 536             }
 537         };
 538     }
 539 
 540     /**
 541      * Called when it is time to actually execute the task (any delay has by now been
 542      * accounted for). Essentially this ends up simply calling the super implementation
 543      * of executeTask and doing some bookkeeping.
 544      *
 545      * @param task must not be null
 546      */
 547     private void executeTaskNow(Task<V> task) {
 548         assert task != null;
 549         lastRunTime = clock();
 550         freshStart = false;
 551         super.executeTask(task);
 552     }
 553 
 554     /**
 555      * Normalize our handling of Durations according to the class documentation.
 556      * @param d can be null
 557      * @return a double representing the millis.
 558      */
 559     private static double normalize(Duration d) {
 560         if (d == null || d.isUnknown()) return 0;
 561         if (d.isIndefinite()) return Double.MAX_VALUE;
 562         return d.toMillis();
 563     }
 564 }